@jonit-dev/night-watch-cli 1.7.24 → 1.7.25
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/dist/shared/types.d.ts +1 -1
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/src/agents/soul-compiler.d.ts.map +1 -1
- package/dist/src/agents/soul-compiler.js +60 -6
- package/dist/src/agents/soul-compiler.js.map +1 -1
- package/dist/src/commands/qa.d.ts +4 -0
- package/dist/src/commands/qa.d.ts.map +1 -1
- package/dist/src/commands/qa.js +35 -0
- package/dist/src/commands/qa.js.map +1 -1
- package/dist/src/commands/serve.d.ts +12 -0
- package/dist/src/commands/serve.d.ts.map +1 -1
- package/dist/src/commands/serve.js +115 -0
- package/dist/src/commands/serve.js.map +1 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +16 -3
- package/dist/src/config.js.map +1 -1
- package/dist/src/slack/channel-manager.js +3 -3
- package/dist/src/slack/channel-manager.js.map +1 -1
- package/dist/src/slack/client.d.ts +2 -1
- package/dist/src/slack/client.d.ts.map +1 -1
- package/dist/src/slack/client.js +20 -2
- package/dist/src/slack/client.js.map +1 -1
- package/dist/src/slack/deliberation.d.ts +17 -1
- package/dist/src/slack/deliberation.d.ts.map +1 -1
- package/dist/src/slack/deliberation.js +220 -46
- package/dist/src/slack/deliberation.js.map +1 -1
- package/dist/src/slack/interaction-listener.d.ts +48 -0
- package/dist/src/slack/interaction-listener.d.ts.map +1 -1
- package/dist/src/slack/interaction-listener.js +793 -12
- package/dist/src/slack/interaction-listener.js.map +1 -1
- package/dist/src/storage/repositories/sqlite/agent-persona-repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/sqlite/agent-persona-repository.js +209 -99
- package/dist/src/storage/repositories/sqlite/agent-persona-repository.js.map +1 -1
- package/dist/src/utils/avatar-generator.d.ts +1 -1
- package/dist/src/utils/avatar-generator.d.ts.map +1 -1
- package/dist/src/utils/avatar-generator.js +55 -15
- package/dist/src/utils/avatar-generator.js.map +1 -1
- package/dist/src/utils/notify.d.ts +1 -0
- package/dist/src/utils/notify.d.ts.map +1 -1
- package/dist/src/utils/notify.js +13 -1
- package/dist/src/utils/notify.js.map +1 -1
- package/package.json +1 -1
- package/scripts/night-watch-pr-reviewer-cron.sh +36 -8
- package/scripts/night-watch-qa-cron.sh +15 -3
- package/templates/night-watch-pr-reviewer.md +46 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-manager.js","sourceRoot":"","sources":["../../../src/slack/channel-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAEnE;;;GAGG;AACH,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI;SACR,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,2DAA2D;AAC9E,CAAC;AAED,MAAM,OAAO,cAAc;IACR,YAAY,CAAc;IAC1B,OAAO,CAAoB;IAE5C,YAAY,WAAwB,EAAE,MAAyB;QAC7D,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,oBAAoB,CAAC,WAAmB,EAAE,WAAmB;QACjE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,yBAAyB,EAAE,CAAC;YACnF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QAE3D,uCAAuC;QACvC,IAAI,OAAO,EAAE,cAAc,EAAE,CAAC;YAC5B,OAAO,OAAO,CAAC,cAAc,CAAC;QAChC,CAAC;QAED,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,QAAQ,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACnD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;YAErE,mBAAmB;YACnB,KAAK,CAAC,eAAe,CAAC,kBAAkB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;YAEjE,iCAAiC;YACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;YAEtE,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;gBACxB,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CACjC,SAAS,EACT,
|
|
1
|
+
{"version":3,"file":"channel-manager.js","sourceRoot":"","sources":["../../../src/slack/channel-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ export interface ISlackChannel {
|
|
|
12
12
|
id: string;
|
|
13
13
|
name: string;
|
|
14
14
|
}
|
|
15
|
+
export declare function getFallbackAvatarUrl(persona: IAgentPersona): string;
|
|
15
16
|
export declare class SlackClient {
|
|
16
17
|
private readonly _client;
|
|
17
18
|
constructor(botToken: string);
|
|
@@ -23,7 +24,7 @@ export declare class SlackClient {
|
|
|
23
24
|
/**
|
|
24
25
|
* Post a simple message to Slack using the bot's default identity.
|
|
25
26
|
*/
|
|
26
|
-
postMessage(channel: string, text: string): Promise<void>;
|
|
27
|
+
postMessage(channel: string, text: string, threadTs?: string): Promise<void>;
|
|
27
28
|
/**
|
|
28
29
|
* Resolve the bot user id for mention detection/filtering.
|
|
29
30
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/slack/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAY;gBAExB,QAAQ,EAAE,MAAM;IAI5B;;;OAGG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC;IA0BzB;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/slack/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAWD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAInE;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAY;gBAExB,QAAQ,EAAE,MAAM;IAI5B;;;OAGG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC;IA0BzB;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlF;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAM5C;;OAEG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;IAQhB;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUlD;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD;;OAEG;IACG,iBAAiB,CACrB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,KAAK,SAAK,GACT,OAAO,CAAC,aAAa,EAAE,CAAC;IAc3B;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAY9C;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IA6B1D;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAiBzE"}
|
package/dist/src/slack/client.js
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
* Uses the official @slack/web-api SDK for type-safe API calls.
|
|
4
4
|
*/
|
|
5
5
|
import { WebClient } from '@slack/web-api';
|
|
6
|
+
function roleAvatarColor(role) {
|
|
7
|
+
const normalized = role.toLowerCase();
|
|
8
|
+
if (normalized.includes('security'))
|
|
9
|
+
return '8b1e2f';
|
|
10
|
+
if (normalized.includes('qa') || normalized.includes('quality'))
|
|
11
|
+
return '0f766e';
|
|
12
|
+
if (normalized.includes('lead') || normalized.includes('architect'))
|
|
13
|
+
return '1d4ed8';
|
|
14
|
+
if (normalized.includes('implementer') || normalized.includes('developer'))
|
|
15
|
+
return '374151';
|
|
16
|
+
return '111827';
|
|
17
|
+
}
|
|
18
|
+
export function getFallbackAvatarUrl(persona) {
|
|
19
|
+
const background = roleAvatarColor(persona.role);
|
|
20
|
+
const name = encodeURIComponent(persona.name.trim() || 'Night Watch');
|
|
21
|
+
return `https://ui-avatars.com/api/?name=${name}&background=${background}&color=ffffff&size=128&bold=true&format=png`;
|
|
22
|
+
}
|
|
6
23
|
export class SlackClient {
|
|
7
24
|
_client;
|
|
8
25
|
constructor(botToken) {
|
|
@@ -16,7 +33,7 @@ export class SlackClient {
|
|
|
16
33
|
// Slack icon_url must be a real HTTP URL — data URIs are not supported
|
|
17
34
|
const iconUrl = persona.avatarUrl && !persona.avatarUrl.startsWith('data:')
|
|
18
35
|
? persona.avatarUrl
|
|
19
|
-
:
|
|
36
|
+
: getFallbackAvatarUrl(persona);
|
|
20
37
|
const result = await this._client.chat.postMessage({
|
|
21
38
|
channel,
|
|
22
39
|
text,
|
|
@@ -36,10 +53,11 @@ export class SlackClient {
|
|
|
36
53
|
/**
|
|
37
54
|
* Post a simple message to Slack using the bot's default identity.
|
|
38
55
|
*/
|
|
39
|
-
async postMessage(channel, text) {
|
|
56
|
+
async postMessage(channel, text, threadTs) {
|
|
40
57
|
await this._client.chat.postMessage({
|
|
41
58
|
channel,
|
|
42
59
|
text,
|
|
60
|
+
thread_ts: threadTs,
|
|
43
61
|
});
|
|
44
62
|
}
|
|
45
63
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/slack/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAc3C,MAAM,OAAO,WAAW;IACL,OAAO,CAAY;IAEpC,YAAY,QAAgB;QAC1B,IAAI,CAAC,OAAO,GAAG,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CACf,OAAe,EACf,IAAY,EACZ,OAAsB,EACtB,QAAiB;QAEjB,uEAAuE;QACvE,MAAM,OAAO,GACX,OAAO,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC;YACzD,CAAC,CAAC,OAAO,CAAC,SAAS;YACnB,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/slack/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAc3C,SAAS,eAAe,CAAC,IAAY;IACnC,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACtC,IAAI,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrD,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,QAAQ,CAAC;IACjF,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrF,IAAI,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC5F,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAsB;IACzD,MAAM,UAAU,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,aAAa,CAAC,CAAC;IACtE,OAAO,oCAAoC,IAAI,eAAe,UAAU,6CAA6C,CAAC;AACxH,CAAC;AAED,MAAM,OAAO,WAAW;IACL,OAAO,CAAY;IAEpC,YAAY,QAAgB;QAC1B,IAAI,CAAC,OAAO,GAAG,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CACf,OAAe,EACf,IAAY,EACZ,OAAsB,EACtB,QAAiB;QAEjB,uEAAuE;QACvE,MAAM,OAAO,GACX,OAAO,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC;YACzD,CAAC,CAAC,OAAO,CAAC,SAAS;YACnB,CAAC,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAEpC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC;YACjD,OAAO;YACP,IAAI;YACJ,QAAQ,EAAE,OAAO,CAAC,IAAI;YACtB,QAAQ,EAAE,OAAO;YACjB,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,oDAAoD,OAAO,GAAG,CAAC,CAAC;QAClF,CAAC;QAED,OAAO;YACL,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,IAAI;SACL,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,IAAY,EAAE,QAAiB;QAChE,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC;YAClC,OAAO;YACP,IAAI;YACJ,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9B,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CACf,OAAe,EACf,SAAiB,EACjB,KAAa;QAEb,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC;YAC/B,OAAO;YACP,SAAS;YACT,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;SAC9B,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB;QACjC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IAChE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CAAC,IAAY;QAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC;YACrD,IAAI,EAAE,IAAI;iBACP,WAAW,EAAE;iBACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;iBAC3B,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;SAChB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,OAAO,EAAE,EAAY,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAAC,SAAiB;QACpC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IACnE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CACrB,OAAe,EACf,QAAgB,EAChB,KAAK,GAAG,EAAE;QAEV,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC;YACtD,OAAO;YACP,EAAE,EAAE,QAAQ;YACZ,KAAK;SACN,CAAC,CAAC;QAEH,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzC,EAAE,EAAE,CAAC,CAAC,EAAY;YAClB,OAAO;YACP,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAW;SAC/B,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC;YACnD,KAAK,EAAE,gBAAgB;YACvB,KAAK,EAAE,GAAG;SACX,CAAC,CAAC;QAEH,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1C,EAAE,EAAE,EAAE,CAAC,EAAY;YACnB,IAAI,EAAE,EAAE,CAAC,IAAc;SACxB,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS;QACb,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwC,CAAC;QAC9D,IAAI,MAA0B,CAAC;QAE/B,GAAG,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;gBAC3C,KAAK,EAAE,GAAG;gBACV,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9B,CAAC,CAAC;YAEH,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;gBAC1C,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC;gBACrB,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,KAAK,WAAW,EAAE,CAAC;oBACjE,SAAS;gBACX,CAAC;gBAED,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE;oBACZ,EAAE;oBACF,IAAI,EAAE,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,IAAI,EAAE,CAAW;iBACxD,CAAC,CAAC;YACL,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,CAAC,iBAAiB,EAAE,WAAW,CAAC;YACzD,MAAM,GAAG,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,CAAC,QAAQ,MAAM,EAAE;QAEjB,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,OAAiB;QACpD,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACjF,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QAEzC,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,iDAAiD;QACjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;YACpD,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;YAC/C,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC;gBACtC,OAAO,EAAE,SAAS;gBAClB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;aACvB,CAAC,CAAC;YACH,YAAY,IAAI,KAAK,CAAC,MAAM,CAAC;QAC/B,CAAC;QAED,OAAO,YAAY,CAAC;IACtB,CAAC;CACF"}
|
|
@@ -6,11 +6,19 @@
|
|
|
6
6
|
import { IAgentPersona, IDiscussionTrigger, ISlackDiscussion } from "../../shared/types.js";
|
|
7
7
|
import { SlackClient } from "./client.js";
|
|
8
8
|
import { INightWatchConfig } from "../types.js";
|
|
9
|
+
interface IHumanizeSlackReplyOptions {
|
|
10
|
+
allowEmoji?: boolean;
|
|
11
|
+
allowNonFacialEmoji?: boolean;
|
|
12
|
+
maxSentences?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function humanizeSlackReply(raw: string, options?: IHumanizeSlackReplyOptions): string;
|
|
9
15
|
export declare class DeliberationEngine {
|
|
10
16
|
private readonly _slackClient;
|
|
11
17
|
private readonly _config;
|
|
12
18
|
private readonly _humanResumeTimers;
|
|
19
|
+
private readonly _emojiCadenceCounter;
|
|
13
20
|
constructor(slackClient: SlackClient, config: INightWatchConfig);
|
|
21
|
+
private _humanizeForPost;
|
|
14
22
|
/**
|
|
15
23
|
* Start a new discussion thread for a trigger event.
|
|
16
24
|
* Posts the opening message and kicks off the first round of contributions.
|
|
@@ -45,6 +53,14 @@ export declare class DeliberationEngine {
|
|
|
45
53
|
* Reply as a persona in any Slack thread — no formal discussion required.
|
|
46
54
|
* Used when someone @mentions a persona outside of a Night Watch discussion.
|
|
47
55
|
*/
|
|
48
|
-
replyAsAgent(channel: string, threadTs: string, incomingText: string, persona: IAgentPersona): Promise<void>;
|
|
56
|
+
replyAsAgent(channel: string, threadTs: string, incomingText: string, persona: IAgentPersona, projectContext?: string): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Generate and post a proactive message from a persona.
|
|
59
|
+
* Used by the interaction listener when a channel has been idle.
|
|
60
|
+
* The persona shares an observation, question, or suggestion based on
|
|
61
|
+
* project context and roadmap state — in their own voice.
|
|
62
|
+
*/
|
|
63
|
+
postProactiveMessage(channel: string, persona: IAgentPersona, projectContext: string, roadmapContext: string): Promise<void>;
|
|
49
64
|
}
|
|
65
|
+
export {};
|
|
50
66
|
//# sourceMappingURL=deliberation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deliberation.d.ts","sourceRoot":"","sources":["../../../src/slack/deliberation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"deliberation.d.ts","sourceRoot":"","sources":["../../../src/slack/deliberation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAQhD,UAAU,0BAA0B;IAClC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAyWD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,0BAA+B,GAAG,MAAM,CAgChG;AAYD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoD;IACvF,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA6B;gBAEtD,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB;IAK/D,OAAO,CAAC,gBAAgB;IAmBxB;;;OAGG;IACG,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAmB/D,wBAAwB;IAqEtC;;OAEG;IACG,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAqDpF;;;OAGG;IACG,kBAAkB,CACtB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IA+ChB;;OAEG;YACW,qBAAqB;IAwDnC;;;;OAIG;YACW,kBAAkB;IAgHhC;;;OAGG;IACG,mBAAmB,CACvB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAqDhB;;;OAGG;IACG,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC;IA0ChB;;;;;OAKG;IACG,oBAAoB,CACxB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,aAAa,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC;CAyCjB"}
|
|
@@ -9,6 +9,7 @@ const MAX_ROUNDS = 3;
|
|
|
9
9
|
const MESSAGE_DELAY_MS = 1500; // Rate limit: 1.5s between posts
|
|
10
10
|
const DISCUSSION_RESUME_DELAY_MS = 60_000;
|
|
11
11
|
const DISCUSSION_REPLAY_GUARD_MS = 30 * 60_000;
|
|
12
|
+
const MAX_HUMANIZED_SENTENCES = 2;
|
|
12
13
|
const inFlightDiscussionStarts = new Map();
|
|
13
14
|
function discussionStartKey(trigger) {
|
|
14
15
|
return `${trigger.projectPath}:${trigger.type}:${trigger.ref}`;
|
|
@@ -36,6 +37,8 @@ function getChannelForTrigger(trigger, config) {
|
|
|
36
37
|
return slack.channels.incidents;
|
|
37
38
|
case 'prd_kickoff':
|
|
38
39
|
return slack.channels.eng; // Callers should populate trigger.channelId with proj channel
|
|
40
|
+
case 'code_watch':
|
|
41
|
+
return slack.channels.eng;
|
|
39
42
|
default:
|
|
40
43
|
return slack.channels.eng;
|
|
41
44
|
}
|
|
@@ -87,6 +90,12 @@ function getParticipatingPersonas(triggerType, personas) {
|
|
|
87
90
|
add(dev);
|
|
88
91
|
add(carlos);
|
|
89
92
|
break;
|
|
93
|
+
case 'code_watch':
|
|
94
|
+
add(dev);
|
|
95
|
+
add(carlos);
|
|
96
|
+
add(maya);
|
|
97
|
+
add(priya);
|
|
98
|
+
break;
|
|
90
99
|
default:
|
|
91
100
|
add(carlos);
|
|
92
101
|
break;
|
|
@@ -145,11 +154,13 @@ function resolvePersonaAIConfig(persona, config) {
|
|
|
145
154
|
function buildOpeningMessage(trigger) {
|
|
146
155
|
switch (trigger.type) {
|
|
147
156
|
case 'pr_review':
|
|
148
|
-
return `
|
|
157
|
+
return `Opened ${trigger.ref}${trigger.prUrl ? ` — ${trigger.prUrl}` : ''}. Ready for eyes.`;
|
|
149
158
|
case 'build_failure':
|
|
150
|
-
return `Build
|
|
159
|
+
return `Build broke on ${trigger.ref}. Looking into it.\n\n${trigger.context.slice(0, 500)}`;
|
|
151
160
|
case 'prd_kickoff':
|
|
152
|
-
return `Picking up
|
|
161
|
+
return `Picking up ${trigger.ref}. Going to start carving out the implementation.`;
|
|
162
|
+
case 'code_watch':
|
|
163
|
+
return `Something caught my eye during a scan — want to get a second opinion on this.\n\n${trigger.context.slice(0, 600)}`;
|
|
153
164
|
default:
|
|
154
165
|
return trigger.context.slice(0, 500);
|
|
155
166
|
}
|
|
@@ -159,28 +170,35 @@ function buildOpeningMessage(trigger) {
|
|
|
159
170
|
* This is what gets sent to the AI provider to generate the agent's message.
|
|
160
171
|
*/
|
|
161
172
|
function buildContributionPrompt(persona, trigger, threadHistory, round) {
|
|
162
|
-
|
|
173
|
+
const isFirstRound = round === 1;
|
|
174
|
+
const isFinalRound = round >= MAX_ROUNDS;
|
|
175
|
+
return `You are ${persona.name}, ${persona.role}.
|
|
176
|
+
You're in a Slack thread with your teammates — Dev (implementer), Carlos (tech lead), Maya (security), and Priya (QA). This is a real conversation, not a report.
|
|
163
177
|
|
|
164
|
-
## Thread Context
|
|
165
178
|
Trigger: ${trigger.type} — ${trigger.ref}
|
|
166
|
-
Round: ${round}
|
|
179
|
+
Round: ${round}/${MAX_ROUNDS}${isFinalRound ? ' (final round — wrap up)' : ''}
|
|
167
180
|
|
|
168
181
|
## Context
|
|
169
182
|
${trigger.context.slice(0, 2000)}
|
|
170
183
|
|
|
171
184
|
## Thread So Far
|
|
172
|
-
${threadHistory || '(
|
|
185
|
+
${threadHistory || '(Thread just started)'}
|
|
173
186
|
|
|
174
|
-
##
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
- If you
|
|
181
|
-
- If you have
|
|
187
|
+
## How to respond
|
|
188
|
+
Write a short Slack message — 1 to 2 sentences. This is chat, not documentation.
|
|
189
|
+
${isFirstRound ? '- First round: give your initial take from your angle. Be specific.' : '- Follow-up round: respond to what others said. Agree, push back, or add something new.'}
|
|
190
|
+
- Talk like a teammate, not an assistant. No pleasantries, no filler.
|
|
191
|
+
- Stay in your lane — only comment on your domain unless something crosses into it.
|
|
192
|
+
- You can name-drop teammates when handing off ("Maya should look at the auth here").
|
|
193
|
+
- If nothing concerns you, a brief "nothing from me" or a short acknowledgment is fine.
|
|
194
|
+
- If you have a concern, name it specifically and suggest a direction.
|
|
195
|
+
- No markdown formatting. No bullet lists. No headings. Just a message.
|
|
196
|
+
- Emojis: use one only if it genuinely fits. Default to none.
|
|
197
|
+
- Never start with "Great question", "Of course", "I hope this helps", or similar.
|
|
198
|
+
- Never say "as an AI" or break character.
|
|
199
|
+
${isFinalRound ? '- Final round: be decisive. State your position clearly.' : ''}
|
|
182
200
|
|
|
183
|
-
Write ONLY your message
|
|
201
|
+
Write ONLY your message. No name prefix, no labels.`;
|
|
184
202
|
}
|
|
185
203
|
/**
|
|
186
204
|
* Call the AI provider to generate an agent contribution.
|
|
@@ -240,14 +258,106 @@ async function callAIForContribution(persona, config, contributionPrompt) {
|
|
|
240
258
|
}
|
|
241
259
|
return `[${persona.name}: No AI provider configured]`;
|
|
242
260
|
}
|
|
261
|
+
const CANNED_PHRASE_PREFIXES = [
|
|
262
|
+
/^great question[,.! ]*/i,
|
|
263
|
+
/^of course[,.! ]*/i,
|
|
264
|
+
/^certainly[,.! ]*/i,
|
|
265
|
+
/^you['’]re absolutely right[,.! ]*/i,
|
|
266
|
+
/^i hope this helps[,.! ]*/i,
|
|
267
|
+
];
|
|
268
|
+
function limitEmojiCount(text, maxEmojis) {
|
|
269
|
+
let seen = 0;
|
|
270
|
+
return text.replace(/[\p{Extended_Pictographic}]/gu, (m) => {
|
|
271
|
+
seen += 1;
|
|
272
|
+
return seen <= maxEmojis ? m : '';
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function isFacialEmoji(char) {
|
|
276
|
+
return /[\u{1F600}-\u{1F64F}\u{1F910}-\u{1F92F}\u{1F970}-\u{1F97A}]/u.test(char);
|
|
277
|
+
}
|
|
278
|
+
function applyEmojiPolicy(text, allowEmoji, allowNonFacialEmoji) {
|
|
279
|
+
if (!allowEmoji) {
|
|
280
|
+
return text.replace(/[\p{Extended_Pictographic}]/gu, '');
|
|
281
|
+
}
|
|
282
|
+
const emojis = Array.from(text.matchAll(/[\p{Extended_Pictographic}]/gu)).map((m) => m[0]);
|
|
283
|
+
if (emojis.length === 0)
|
|
284
|
+
return text;
|
|
285
|
+
const chosenFacial = emojis.find((e) => isFacialEmoji(e));
|
|
286
|
+
const chosen = chosenFacial ?? (allowNonFacialEmoji ? emojis[0] : null);
|
|
287
|
+
if (!chosen) {
|
|
288
|
+
return text.replace(/[\p{Extended_Pictographic}]/gu, '');
|
|
289
|
+
}
|
|
290
|
+
let kept = false;
|
|
291
|
+
return text.replace(/[\p{Extended_Pictographic}]/gu, (e) => {
|
|
292
|
+
if (!kept && e === chosen) {
|
|
293
|
+
kept = true;
|
|
294
|
+
return e;
|
|
295
|
+
}
|
|
296
|
+
return '';
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function trimToSentences(text, maxSentences) {
|
|
300
|
+
const parts = text
|
|
301
|
+
.split(/(?<=[.!?])\s+/)
|
|
302
|
+
.map((s) => s.trim())
|
|
303
|
+
.filter(Boolean);
|
|
304
|
+
if (parts.length <= maxSentences)
|
|
305
|
+
return text.trim();
|
|
306
|
+
return parts.slice(0, maxSentences).join(' ').trim();
|
|
307
|
+
}
|
|
308
|
+
export function humanizeSlackReply(raw, options = {}) {
|
|
309
|
+
const { allowEmoji = true, allowNonFacialEmoji = true, maxSentences = MAX_HUMANIZED_SENTENCES, } = options;
|
|
310
|
+
let text = raw.trim();
|
|
311
|
+
if (!text)
|
|
312
|
+
return text;
|
|
313
|
+
// Remove markdown formatting artifacts that look templated in chat.
|
|
314
|
+
text = text
|
|
315
|
+
.replace(/^#{1,6}\s+/gm, '')
|
|
316
|
+
.replace(/^\s*[-*]\s+/gm, '')
|
|
317
|
+
.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
318
|
+
.replace(/\s+/g, ' ')
|
|
319
|
+
.trim();
|
|
320
|
+
// Strip common assistant-y openers.
|
|
321
|
+
for (const pattern of CANNED_PHRASE_PREFIXES) {
|
|
322
|
+
text = text.replace(pattern, '').trim();
|
|
323
|
+
}
|
|
324
|
+
text = applyEmojiPolicy(text, allowEmoji, allowNonFacialEmoji);
|
|
325
|
+
text = limitEmojiCount(text, 1);
|
|
326
|
+
text = trimToSentences(text, maxSentences);
|
|
327
|
+
if (text.length > 260) {
|
|
328
|
+
text = `${text.slice(0, 257).trimEnd()}...`;
|
|
329
|
+
}
|
|
330
|
+
return text;
|
|
331
|
+
}
|
|
332
|
+
function buildCurrentCliInvocation(args) {
|
|
333
|
+
const cliEntry = process.argv[1];
|
|
334
|
+
if (!cliEntry)
|
|
335
|
+
return null;
|
|
336
|
+
return [...process.execArgv, cliEntry, ...args];
|
|
337
|
+
}
|
|
338
|
+
function formatCommandForLog(bin, args) {
|
|
339
|
+
return [bin, ...args].map((part) => JSON.stringify(part)).join(' ');
|
|
340
|
+
}
|
|
243
341
|
export class DeliberationEngine {
|
|
244
342
|
_slackClient;
|
|
245
343
|
_config;
|
|
246
344
|
_humanResumeTimers = new Map();
|
|
345
|
+
_emojiCadenceCounter = new Map();
|
|
247
346
|
constructor(slackClient, config) {
|
|
248
347
|
this._slackClient = slackClient;
|
|
249
348
|
this._config = config;
|
|
250
349
|
}
|
|
350
|
+
_humanizeForPost(channel, threadTs, persona, raw) {
|
|
351
|
+
const key = `${channel}:${threadTs}:${persona.id}`;
|
|
352
|
+
const count = (this._emojiCadenceCounter.get(key) ?? 0) + 1;
|
|
353
|
+
this._emojiCadenceCounter.set(key, count);
|
|
354
|
+
// Human cadence:
|
|
355
|
+
// - emoji roughly every 3rd message by same persona in same thread
|
|
356
|
+
// - non-facial emoji much rarer (roughly every 9th message)
|
|
357
|
+
const allowEmoji = count % 3 === 0;
|
|
358
|
+
const allowNonFacialEmoji = count % 9 === 0;
|
|
359
|
+
return humanizeSlackReply(raw, { allowEmoji, allowNonFacialEmoji, maxSentences: 2 });
|
|
360
|
+
}
|
|
251
361
|
/**
|
|
252
362
|
* Start a new discussion thread for a trigger event.
|
|
253
363
|
* Posts the opening message and kicks off the first round of contributions.
|
|
@@ -352,7 +462,8 @@ export class DeliberationEngine {
|
|
|
352
462
|
message = `[Contribution from ${persona.name} unavailable — AI provider not configured]`;
|
|
353
463
|
}
|
|
354
464
|
if (message) {
|
|
355
|
-
|
|
465
|
+
const finalMessage = this._humanizeForPost(discussion.channelId, discussion.threadTs, persona, message);
|
|
466
|
+
await this._slackClient.postAsAgent(discussion.channelId, finalMessage, persona, discussion.threadTs);
|
|
356
467
|
repos.slackDiscussion.addParticipant(discussionId, persona.id);
|
|
357
468
|
await sleep(MESSAGE_DELAY_MS);
|
|
358
469
|
}
|
|
@@ -383,7 +494,7 @@ export class DeliberationEngine {
|
|
|
383
494
|
const updated = innerRepos.slackDiscussion.getById(discussion.id);
|
|
384
495
|
if (!updated || updated.status !== 'active')
|
|
385
496
|
return;
|
|
386
|
-
await this._slackClient.postAsAgent(channel, "
|
|
497
|
+
await this._slackClient.postAsAgent(channel, "Ok, picking this back up. Let me see where we landed.", carlos, threadTs);
|
|
387
498
|
await sleep(MESSAGE_DELAY_MS);
|
|
388
499
|
await this._evaluateConsensus(discussion.id, {
|
|
389
500
|
type: discussion.triggerType,
|
|
@@ -421,9 +532,10 @@ export class DeliberationEngine {
|
|
|
421
532
|
message = '';
|
|
422
533
|
}
|
|
423
534
|
if (message) {
|
|
424
|
-
|
|
535
|
+
const finalMessage = this._humanizeForPost(discussion.channelId, discussion.threadTs, persona, message);
|
|
536
|
+
await this._slackClient.postAsAgent(discussion.channelId, finalMessage, persona, discussion.threadTs);
|
|
425
537
|
repos.slackDiscussion.addParticipant(discussionId, persona.id);
|
|
426
|
-
historyText = historyText ? `${historyText}\n---\n${
|
|
538
|
+
historyText = historyText ? `${historyText}\n---\n${finalMessage}` : finalMessage;
|
|
427
539
|
await sleep(MESSAGE_DELAY_MS);
|
|
428
540
|
}
|
|
429
541
|
}
|
|
@@ -449,19 +561,21 @@ export class DeliberationEngine {
|
|
|
449
561
|
// Get thread history and let Carlos evaluate
|
|
450
562
|
const history = await this._slackClient.getChannelHistory(discussion.channelId, discussion.threadTs, 20);
|
|
451
563
|
const historyText = history.map(m => m.text).join('\n---\n');
|
|
452
|
-
const consensusPrompt = `You are ${carlos.name}, ${carlos.role}.
|
|
453
|
-
|
|
454
|
-
Review this discussion thread and decide: are we ready to ship, do we need another round of review, or do we need a human?
|
|
564
|
+
const consensusPrompt = `You are ${carlos.name}, ${carlos.role}. You're wrapping up a team discussion.
|
|
455
565
|
|
|
456
566
|
Thread:
|
|
457
567
|
${historyText}
|
|
458
568
|
|
|
459
|
-
Round: ${discussion.round}
|
|
569
|
+
Round: ${discussion.round}/${MAX_ROUNDS}
|
|
570
|
+
|
|
571
|
+
Make the call. Are we done, do we need another pass, or does a human need to weigh in?
|
|
572
|
+
|
|
573
|
+
Respond with EXACTLY one of these formats (include the prefix):
|
|
574
|
+
- APPROVE: [short closing message in your voice — e.g., "Clean. Let's ship it."]
|
|
575
|
+
- CHANGES: [what specifically still needs work — be concrete, not vague]
|
|
576
|
+
- HUMAN: [why this needs a human decision — be specific about what's ambiguous]
|
|
460
577
|
|
|
461
|
-
|
|
462
|
-
- APPROVE: [your short closing message, e.g., "LGTM 👍 Ship it 🚀"]
|
|
463
|
-
- CHANGES: [summary of what still needs to change — be specific]
|
|
464
|
-
- HUMAN: [why you need a human decision]`;
|
|
578
|
+
Write the prefix and your message. Nothing else.`;
|
|
465
579
|
let decision;
|
|
466
580
|
try {
|
|
467
581
|
decision = await callAIForContribution(carlos, this._config, consensusPrompt);
|
|
@@ -470,14 +584,14 @@ Respond with ONLY one of:
|
|
|
470
584
|
decision = 'HUMAN: AI evaluation failed — needs manual review';
|
|
471
585
|
}
|
|
472
586
|
if (decision.startsWith('APPROVE')) {
|
|
473
|
-
const message = decision.replace(/^APPROVE:\s*/, '').trim() || 'Ship it
|
|
587
|
+
const message = decision.replace(/^APPROVE:\s*/, '').trim() || 'Clean. Ship it.';
|
|
474
588
|
await this._slackClient.postAsAgent(discussion.channelId, message, carlos, discussion.threadTs);
|
|
475
589
|
repos.slackDiscussion.updateStatus(discussionId, 'consensus', 'approved');
|
|
476
590
|
return;
|
|
477
591
|
}
|
|
478
592
|
if (decision.startsWith('CHANGES') && discussion.round < MAX_ROUNDS) {
|
|
479
593
|
const changes = decision.replace(/^CHANGES:\s*/, '').trim();
|
|
480
|
-
await this._slackClient.postAsAgent(discussion.channelId,
|
|
594
|
+
await this._slackClient.postAsAgent(discussion.channelId, changes, carlos, discussion.threadTs);
|
|
481
595
|
await sleep(MESSAGE_DELAY_MS);
|
|
482
596
|
// Increment round and start another contribution round, then loop back.
|
|
483
597
|
const nextRound = discussion.round + 1;
|
|
@@ -491,7 +605,7 @@ Respond with ONLY one of:
|
|
|
491
605
|
if (decision.startsWith('CHANGES') && discussion.round >= MAX_ROUNDS) {
|
|
492
606
|
// Max rounds reached — set changes_requested and optionally trigger PR refinement
|
|
493
607
|
const changesSummary = decision.replace(/^CHANGES:\s*/, '').trim();
|
|
494
|
-
await this._slackClient.postAsAgent(discussion.channelId,
|
|
608
|
+
await this._slackClient.postAsAgent(discussion.channelId, `We've been at this for ${MAX_ROUNDS} rounds. Sending it through with the remaining notes — Dev can address them in the next pass.`, carlos, discussion.threadTs);
|
|
495
609
|
repos.slackDiscussion.updateStatus(discussionId, 'consensus', 'changes_requested');
|
|
496
610
|
if (discussion.triggerType === 'pr_review') {
|
|
497
611
|
await this.triggerPRRefinement(discussionId, changesSummary, discussion.triggerRef).catch(e => console.warn('PR refinement trigger failed:', e));
|
|
@@ -499,7 +613,10 @@ Respond with ONLY one of:
|
|
|
499
613
|
return;
|
|
500
614
|
}
|
|
501
615
|
// HUMAN or fallback
|
|
502
|
-
|
|
616
|
+
const humanReason = decision.replace(/^HUMAN:\s*/, '').trim();
|
|
617
|
+
await this._slackClient.postAsAgent(discussion.channelId, humanReason
|
|
618
|
+
? `Need a human on this one — ${humanReason}`
|
|
619
|
+
: 'This needs a human call. Flagging it.', carlos, discussion.threadTs);
|
|
503
620
|
repos.slackDiscussion.updateStatus(discussionId, 'blocked', 'human_needed');
|
|
504
621
|
return;
|
|
505
622
|
}
|
|
@@ -515,31 +632,36 @@ Respond with ONLY one of:
|
|
|
515
632
|
return;
|
|
516
633
|
const personas = repos.agentPersona.getActive();
|
|
517
634
|
const carlos = findCarlos(personas) ?? personas[0];
|
|
518
|
-
const
|
|
635
|
+
const actor = carlos?.name ?? 'Night Watch';
|
|
519
636
|
if (carlos) {
|
|
520
|
-
await this._slackClient.postAsAgent(discussion.channelId, `Sending
|
|
637
|
+
await this._slackClient.postAsAgent(discussion.channelId, `Sending PR #${prNumber} back through with the notes.`, carlos, discussion.threadTs);
|
|
521
638
|
await sleep(MESSAGE_DELAY_MS);
|
|
522
639
|
}
|
|
523
640
|
// Set NW_SLACK_FEEDBACK and trigger reviewer
|
|
524
641
|
const feedback = JSON.stringify({ discussionId, prNumber, changes: changesSummary });
|
|
642
|
+
const invocationArgs = buildCurrentCliInvocation(['review']);
|
|
643
|
+
if (!invocationArgs) {
|
|
644
|
+
console.warn(`[slack][job] triggerPRRefinement reviewer spawn failed via ${actor} pr=${prNumber}: CLI entry path unavailable`);
|
|
645
|
+
if (carlos) {
|
|
646
|
+
await this._slackClient.postAsAgent(discussion.channelId, `Can't start the reviewer right now — runtime issue. Will retry.`, carlos, discussion.threadTs);
|
|
647
|
+
}
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
console.log(`[slack][job] triggerPRRefinement reviewer spawn via ${actor} pr=${prNumber} cmd=${formatCommandForLog(process.execPath, invocationArgs)}`);
|
|
525
651
|
// Spawn the reviewer as a detached process
|
|
526
652
|
const { spawn } = await import('child_process');
|
|
527
|
-
const reviewer = spawn(process.execPath,
|
|
653
|
+
const reviewer = spawn(process.execPath, invocationArgs, {
|
|
528
654
|
detached: true,
|
|
529
655
|
stdio: 'ignore',
|
|
530
|
-
env: { ...process.env, NW_SLACK_FEEDBACK: feedback },
|
|
656
|
+
env: { ...process.env, NW_SLACK_FEEDBACK: feedback, NW_TARGET_PR: prNumber },
|
|
531
657
|
});
|
|
532
658
|
reviewer.unref();
|
|
533
|
-
// Post update
|
|
534
|
-
if (dev) {
|
|
535
|
-
await this._slackClient.postAsAgent(discussion.channelId, `Reviewer agent kicked off for PR #${prNumber} 🔨 Will post back when done.`, dev, discussion.threadTs);
|
|
536
|
-
}
|
|
537
659
|
}
|
|
538
660
|
/**
|
|
539
661
|
* Reply as a persona in any Slack thread — no formal discussion required.
|
|
540
662
|
* Used when someone @mentions a persona outside of a Night Watch discussion.
|
|
541
663
|
*/
|
|
542
|
-
async replyAsAgent(channel, threadTs, incomingText, persona) {
|
|
664
|
+
async replyAsAgent(channel, threadTs, incomingText, persona, projectContext) {
|
|
543
665
|
let history = [];
|
|
544
666
|
try {
|
|
545
667
|
history = await this._slackClient.getChannelHistory(channel, threadTs, 10);
|
|
@@ -549,10 +671,18 @@ Respond with ONLY one of:
|
|
|
549
671
|
}
|
|
550
672
|
const historyText = history.map((m) => m.text).join('\n---\n');
|
|
551
673
|
const prompt = `You are ${persona.name}, ${persona.role}.\n` +
|
|
552
|
-
(
|
|
553
|
-
(
|
|
554
|
-
`
|
|
555
|
-
`
|
|
674
|
+
`Your teammates: Dev (implementer), Carlos (tech lead), Maya (security), Priya (QA).\n\n` +
|
|
675
|
+
(projectContext ? `Project context: ${projectContext}\n\n` : '') +
|
|
676
|
+
(historyText ? `Thread so far:\n${historyText}\n\n` : '') +
|
|
677
|
+
`Latest message: "${incomingText}"\n\n` +
|
|
678
|
+
`Respond in your own voice. This is Slack — keep it to 1-2 sentences.\n` +
|
|
679
|
+
`- Talk like a colleague, not a bot. No "Great question", "Of course", or "I hope this helps".\n` +
|
|
680
|
+
`- You can tag teammates by name if someone else should weigh in.\n` +
|
|
681
|
+
`- No markdown formatting, headings, or bullet lists.\n` +
|
|
682
|
+
`- Emojis: one max, only if it fits naturally. Default to none.\n` +
|
|
683
|
+
`- If the question is outside your domain, say so briefly and point to the right person.\n` +
|
|
684
|
+
`- If you disagree, say why in one line. If you agree, keep it short.\n\n` +
|
|
685
|
+
`Write only your reply. No name prefix.`;
|
|
556
686
|
let message;
|
|
557
687
|
try {
|
|
558
688
|
message = await callAIForContribution(persona, this._config, prompt);
|
|
@@ -561,7 +691,51 @@ Respond with ONLY one of:
|
|
|
561
691
|
message = `[Reply from ${persona.name} unavailable — AI provider not configured]`;
|
|
562
692
|
}
|
|
563
693
|
if (message) {
|
|
564
|
-
await this._slackClient.postAsAgent(channel, message, persona, threadTs);
|
|
694
|
+
await this._slackClient.postAsAgent(channel, this._humanizeForPost(channel, threadTs, persona, message), persona, threadTs);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Generate and post a proactive message from a persona.
|
|
699
|
+
* Used by the interaction listener when a channel has been idle.
|
|
700
|
+
* The persona shares an observation, question, or suggestion based on
|
|
701
|
+
* project context and roadmap state — in their own voice.
|
|
702
|
+
*/
|
|
703
|
+
async postProactiveMessage(channel, persona, projectContext, roadmapContext) {
|
|
704
|
+
const prompt = `You are ${persona.name}, ${persona.role}.\n` +
|
|
705
|
+
`Your teammates: Dev (implementer), Carlos (tech lead), Maya (security), Priya (QA).\n\n` +
|
|
706
|
+
`You're posting an unprompted message in the team's Slack channel. ` +
|
|
707
|
+
`The channel has been quiet — you want to share something useful, not just fill silence.\n\n` +
|
|
708
|
+
(projectContext ? `Project context: ${projectContext}\n\n` : '') +
|
|
709
|
+
(roadmapContext ? `Roadmap/PRD status:\n${roadmapContext}\n\n` : '') +
|
|
710
|
+
`Write a SHORT proactive message (1-2 sentences) that does ONE of these:\n` +
|
|
711
|
+
`- Question a roadmap priority or ask if something should be reordered\n` +
|
|
712
|
+
`- Flag something you've been thinking about from your domain (security concern, test gap, architectural question, implementation idea)\n` +
|
|
713
|
+
`- Suggest an improvement or raise a "have we thought about..." question\n` +
|
|
714
|
+
`- Share a concrete observation about the current state of the project\n` +
|
|
715
|
+
`- Offer to kick off a task: "I can run a review on X if nobody's on it"\n\n` +
|
|
716
|
+
`Rules:\n` +
|
|
717
|
+
`- Stay in your lane. Only bring up things relevant to your expertise.\n` +
|
|
718
|
+
`- Be specific — name the feature, file, or concern. No vague "we should think about things."\n` +
|
|
719
|
+
`- Sound like a teammate dropping a thought in chat, not making an announcement.\n` +
|
|
720
|
+
`- No markdown, headings, bullets. Just a message.\n` +
|
|
721
|
+
`- No "Great question", "Just checking in", or "Hope everyone is doing well."\n` +
|
|
722
|
+
`- Emojis: one max, only if natural. Default to none.\n` +
|
|
723
|
+
`- If you genuinely have nothing useful to say, write exactly: SKIP\n\n` +
|
|
724
|
+
`Write only your message. No name prefix.`;
|
|
725
|
+
let message;
|
|
726
|
+
try {
|
|
727
|
+
message = await callAIForContribution(persona, this._config, prompt);
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
return; // Silently skip — proactive messages are optional
|
|
731
|
+
}
|
|
732
|
+
if (!message || message.trim().toUpperCase() === 'SKIP') {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const dummyTs = `${Date.now()}`;
|
|
736
|
+
const finalMessage = this._humanizeForPost(channel, dummyTs, persona, message);
|
|
737
|
+
if (finalMessage) {
|
|
738
|
+
await this._slackClient.postAsAgent(channel, finalMessage, persona);
|
|
565
739
|
}
|
|
566
740
|
}
|
|
567
741
|
}
|