github-issue-tower-defence-management 1.122.6 → 1.122.8
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/CHANGELOG.md +14 -0
- package/bin/domain/usecases/DefaultSilentSessionMessageComposer.js +17 -18
- package/bin/domain/usecases/DefaultSilentSessionMessageComposer.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +6 -4
- package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.test.ts +4 -9
- package/src/domain/usecases/DefaultSilentSessionMessageComposer.test.ts +84 -123
- package/src/domain/usecases/DefaultSilentSessionMessageComposer.ts +21 -18
- package/types/domain/usecases/DefaultSilentSessionMessageComposer.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.122.8](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.122.7...v1.122.8) (2026-07-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* instruct unconditional re-raise in stale-owner-call reminder ([#1168](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1168)) ([f246cd3](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/f246cd385befd0f29c51576593a1ecb2fd23bf40))
|
|
7
|
+
|
|
8
|
+
## [1.122.7](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.122.6...v1.122.7) (2026-07-14)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **silent-monitor:** shorten silent-session reminder defaults to a calm neutral status notice ([#1166](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1166)) ([3f29b69](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/3f29b69065c005b8d41db3e68ead624f9c26ef95))
|
|
14
|
+
|
|
1
15
|
## [1.122.6](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.122.5...v1.122.6) (2026-07-13)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -9,45 +9,44 @@ const formatMinutes = (seconds) => {
|
|
|
9
9
|
const composeIdleSubAgentSection = (idleSubAgents) => {
|
|
10
10
|
const lines = idleSubAgents.map((subAgent) => `- ${subAgent.label}: no output for ${formatMinutes(subAgent.silentSeconds)}`);
|
|
11
11
|
return [
|
|
12
|
-
`${silentSessionReminderSentinel_1.SILENT_SESSION_REMINDER_SENTINEL}
|
|
12
|
+
`${silentSessionReminderSentinel_1.SILENT_SESSION_REMINDER_SENTINEL} This is an automated status check. The following sub-process(es) have produced no output for about the minutes shown, measured from their last tool activity:`,
|
|
13
13
|
...lines,
|
|
14
|
-
'For each one
|
|
14
|
+
'For each one: if it is waiting on an external dependency (a continuous-integration run, an external API, or another process), no action is needed — please log one line noting the wait. If it appears stuck, please restart it, hand it off, or replace it.',
|
|
15
15
|
].join('\n');
|
|
16
16
|
};
|
|
17
17
|
const composeLongRunningSubAgentSection = (longRunningSubAgents) => {
|
|
18
18
|
const lines = longRunningSubAgents.map((subAgent) => `- ${subAgent.label}: running for ${formatMinutes(subAgent.runningSeconds)}`);
|
|
19
19
|
return [
|
|
20
|
-
`${silentSessionReminderSentinel_1.SILENT_SESSION_REMINDER_SENTINEL} The following sub-process(es) have been running longer than a task
|
|
20
|
+
`${silentSessionReminderSentinel_1.SILENT_SESSION_REMINDER_SENTINEL} This is an automated status check. The following sub-process(es) have been running longer than a task usually takes:`,
|
|
21
21
|
...lines,
|
|
22
|
-
'
|
|
22
|
+
'If one is not making progress toward completion, consider breaking the task down, restarting it, handing it off, or replacing it. If it is progressing normally, no action is needed.',
|
|
23
23
|
].join('\n');
|
|
24
24
|
};
|
|
25
25
|
const composeOwnerCallFormatGuidance = (ownerCallMarker) => {
|
|
26
|
-
const
|
|
27
|
-
? `
|
|
28
|
-
:
|
|
29
|
-
return
|
|
26
|
+
const tagLabel = ownerCallMarker !== null && ownerCallMarker.length > 0
|
|
27
|
+
? ` "${ownerCallMarker}"`
|
|
28
|
+
: '';
|
|
29
|
+
return `Format reminder: write the owner-call marker tag${tagLabel} as a complete opening and closing pair on one line, with the content starting immediately with the 🔴 emoji and written to be self-contained, so the owner can understand the situation, the ask, and any decision needed from that single message.`;
|
|
30
30
|
};
|
|
31
31
|
exports.composeOwnerCallFormatGuidance = composeOwnerCallFormatGuidance;
|
|
32
32
|
const composeMainStalledMessage = (mainSilentSeconds, ownerCallMarker) => {
|
|
33
33
|
const minutes = Math.floor(mainSilentSeconds / 60);
|
|
34
34
|
return [
|
|
35
|
-
`${silentSessionReminderSentinel_1.SILENT_SESSION_REMINDER_SENTINEL}
|
|
36
|
-
`1.
|
|
37
|
-
`2.
|
|
38
|
-
`3.
|
|
39
|
-
`4.
|
|
40
|
-
`
|
|
35
|
+
`${silentSessionReminderSentinel_1.SILENT_SESSION_REMINDER_SENTINEL} This is an automated status check. No output has been observed for ${minutes} minutes. If you are waiting on an external process, no action is needed — please log one line explaining the wait. Otherwise please continue with the next step, with these points in mind:`,
|
|
36
|
+
`1. Keep the session task list current, marking finished items as done.`,
|
|
37
|
+
`2. Run independent pieces of work in parallel across sub-agents.`,
|
|
38
|
+
`3. Keep a monitor in place that notices when a sub-agent has produced no output for about 5 minutes.`,
|
|
39
|
+
`4. When an owner decision is needed, or when an owner request has been completed or answered, please share it through a new owner-call — the owner is notified only when one is raised. ${(0, exports.composeOwnerCallFormatGuidance)(ownerCallMarker)}`,
|
|
40
|
+
`Please also include in your next output an estimate of the remaining minutes to finish all tasks.`,
|
|
41
41
|
].join('\n');
|
|
42
42
|
};
|
|
43
43
|
const composeMainStalledWithStaleOwnerCallMessage = (mainSilentSeconds, unansweredOwnerCallAgeSeconds, ownerCallMarker) => {
|
|
44
44
|
const silentMinutes = Math.floor(mainSilentSeconds / 60);
|
|
45
45
|
const ownerCallAgeMinutes = Math.floor(unansweredOwnerCallAgeSeconds / 60);
|
|
46
46
|
return [
|
|
47
|
-
`${silentSessionReminderSentinel_1.SILENT_SESSION_REMINDER_SENTINEL}
|
|
48
|
-
`
|
|
49
|
-
`
|
|
50
|
-
`This reminder does not notify the owner; only a fresh owner call from you surfaces this session to the owner. Also, in your next output, report an estimate of how many minutes you expect to need to finish all remaining tasks.`,
|
|
47
|
+
`${silentSessionReminderSentinel_1.SILENT_SESSION_REMINDER_SENTINEL} This is an automated status check. No output has been observed for ${silentMinutes} minutes, and the owner call raised ${ownerCallAgeMinutes} minutes ago has not yet been acknowledged by the owner.`,
|
|
48
|
+
`An owner call without an acknowledgment — whether it carried a completion report, a question, or a decision request — is still awaiting the owner's acknowledgment, and the earlier message may have been missed. Please re-raise its content as a fresh, self-contained owner call. ${(0, exports.composeOwnerCallFormatGuidance)(ownerCallMarker)}`,
|
|
49
|
+
`Please also include in your next output an estimate of the remaining minutes to finish all tasks.`,
|
|
51
50
|
].join('\n');
|
|
52
51
|
};
|
|
53
52
|
class DefaultSilentSessionMessageComposer {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultSilentSessionMessageComposer.js","sourceRoot":"","sources":["../../../src/domain/usecases/DefaultSilentSessionMessageComposer.ts"],"names":[],"mappings":";;;AAKA,mFAAmF;AAEnF,MAAM,aAAa,GAAG,CAAC,OAAe,EAAU,EAAE;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACzC,OAAO,GAAG,OAAO,GAAG,CAAC;AACvB,CAAC,CAAC;AAEF,MAAM,0BAA0B,GAAG,CACjC,aAAiC,EACzB,EAAE;IACV,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAC7B,CAAC,QAAQ,EAAE,EAAE,CACX,KAAK,QAAQ,CAAC,KAAK,mBAAmB,aAAa,CACjD,QAAQ,CAAC,aAAa,CACvB,EAAE,CACN,CAAC;IACF,OAAO;QACL,GAAG,gEAAgC,
|
|
1
|
+
{"version":3,"file":"DefaultSilentSessionMessageComposer.js","sourceRoot":"","sources":["../../../src/domain/usecases/DefaultSilentSessionMessageComposer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -159,7 +159,7 @@ describe('notifySilentTmuxSessions', () => {
|
|
|
159
159
|
(call) => call[0] === 'tmux' && call[1][0] === 'send-keys',
|
|
160
160
|
);
|
|
161
161
|
expect(sendCall?.[1][2]).toBe(SESSION_NAME);
|
|
162
|
-
expect(sendCall?.[1][4]).toContain('
|
|
162
|
+
expect(sendCall?.[1][4]).toContain('No output has been observed for');
|
|
163
163
|
});
|
|
164
164
|
|
|
165
165
|
it('does not notify a silent github-named live session on its first candidate cycle', async () => {
|
|
@@ -234,7 +234,7 @@ describe('notifySilentTmuxSessions', () => {
|
|
|
234
234
|
`${SILENT_SESSION_REMINDER_SENTINEL} CUSTOM_MAIN_TEMPLATE`,
|
|
235
235
|
);
|
|
236
236
|
expect(sendCall?.[1][4]).toContain(
|
|
237
|
-
'
|
|
237
|
+
'complete opening and closing pair on one line',
|
|
238
238
|
);
|
|
239
239
|
});
|
|
240
240
|
|
|
@@ -308,10 +308,12 @@ describe('notifySilentTmuxSessions', () => {
|
|
|
308
308
|
(call) => call[0] === 'tmux' && call[1][0] === 'send-keys',
|
|
309
309
|
);
|
|
310
310
|
expect(sendCall?.[1][2]).toBe(SESSION_NAME);
|
|
311
|
-
expect(sendCall?.[1][4]).toContain('
|
|
311
|
+
expect(sendCall?.[1][4]).toContain('has not yet been acknowledged');
|
|
312
312
|
expect(sendCall?.[1][4]).toContain(
|
|
313
|
-
're-raise
|
|
313
|
+
're-raise its content as a fresh, self-contained owner call',
|
|
314
314
|
);
|
|
315
|
+
expect(sendCall?.[1][4]).not.toContain('no longer blocked');
|
|
316
|
+
expect(sendCall?.[1][4]).not.toContain('continue with the next step');
|
|
315
317
|
});
|
|
316
318
|
|
|
317
319
|
it('defers the first cycle then notifies on the next cycle once the persisted candidate state confirms a second consecutive cycle', async () => {
|
|
@@ -47,7 +47,7 @@ describe('ConfigurableSilentSessionMessageComposer', () => {
|
|
|
47
47
|
expect(fallback.composeMainStalledSection).not.toHaveBeenCalled();
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
it('appends the owner-call format guidance, including the self-contained
|
|
50
|
+
it('appends the owner-call format guidance, including the self-contained requirement, to the configured main template', () => {
|
|
51
51
|
const fallback = createFallback();
|
|
52
52
|
const composer = new ConfigurableSilentSessionMessageComposer(
|
|
53
53
|
{
|
|
@@ -60,15 +60,10 @@ describe('ConfigurableSilentSessionMessageComposer', () => {
|
|
|
60
60
|
const section = composer.composeMainStalledSection(600);
|
|
61
61
|
expect(section).toContain('CUSTOM_MAIN');
|
|
62
62
|
expect(section).toContain(
|
|
63
|
-
'
|
|
63
|
+
'owner-call marker tag "<<OWNER_CALL>>" as a complete opening and closing pair on one line',
|
|
64
64
|
);
|
|
65
65
|
expect(section).toContain('🔴');
|
|
66
|
-
expect(section).toContain(
|
|
67
|
-
'Make the owner-call message fully self-contained: the owner should be able to understand the whole situation — what happened, what you are asking, and any decision needed — from this single latest owner-call message alone, without reading or scrolling back to earlier messages.',
|
|
68
|
-
);
|
|
69
|
-
expect(section).toContain(
|
|
70
|
-
'Please avoid telling the owner to scroll up, go back, or read previous or above messages; if context is needed, restate it inside the owner-call message itself.',
|
|
71
|
-
);
|
|
66
|
+
expect(section).toContain('written to be self-contained');
|
|
72
67
|
});
|
|
73
68
|
|
|
74
69
|
it('uses the fallback stale-owner-call section when no stale-owner-call template is configured', () => {
|
|
@@ -102,7 +97,7 @@ describe('ConfigurableSilentSessionMessageComposer', () => {
|
|
|
102
97
|
expect(section).toContain('CUSTOM_STALE_OWNER_CALL');
|
|
103
98
|
expect(section).toContain(SILENT_SESSION_REMINDER_SENTINEL);
|
|
104
99
|
expect(section).toContain(
|
|
105
|
-
'
|
|
100
|
+
'owner-call marker tag "<<OWNER_CALL>>" as a complete opening and closing pair on one line',
|
|
106
101
|
);
|
|
107
102
|
expect(
|
|
108
103
|
fallback.composeMainStalledWithStaleOwnerCallSection,
|
|
@@ -23,78 +23,61 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
23
23
|
expect(section).toContain(SILENT_SESSION_REMINDER_SENTINEL);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it('renders the
|
|
26
|
+
it('renders the main-stalled message as a neutral automated status notice with the silent minutes substituted', () => {
|
|
27
27
|
const section = composer.composeMainStalledSection(600);
|
|
28
|
-
expect(section).toContain('
|
|
29
|
-
expect(section).toContain('
|
|
28
|
+
expect(section).toContain('This is an automated status check.');
|
|
29
|
+
expect(section).toContain('No output has been observed for 10 minutes.');
|
|
30
30
|
expect(section).toContain(
|
|
31
|
-
'
|
|
31
|
+
'If you are waiting on an external process, no action is needed — please log one line explaining the wait.',
|
|
32
32
|
);
|
|
33
|
-
expect(section).toContain('
|
|
34
|
-
expect(section).toContain('Self-check now:');
|
|
33
|
+
expect(section).toContain('Otherwise please continue with the next step');
|
|
35
34
|
expect(section).toContain('1.');
|
|
36
35
|
expect(section).toContain('2.');
|
|
37
36
|
expect(section).toContain('3.');
|
|
38
37
|
expect(section).toContain('4.');
|
|
39
|
-
expect(section).toContain(
|
|
40
|
-
'report an estimate of how many minutes you expect to need',
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('floors the silent seconds to whole minutes', () => {
|
|
45
|
-
const section = composer.composeMainStalledSection(659);
|
|
46
|
-
expect(section).toContain('You have produced no output for 10 minutes.');
|
|
47
38
|
});
|
|
48
39
|
|
|
49
|
-
it('
|
|
40
|
+
it('states each of the four checkpoints exactly once', () => {
|
|
50
41
|
const section = composer.composeMainStalledSection(600);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
);
|
|
55
|
-
expect(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
);
|
|
42
|
+
const occurrences = (needle: string): number =>
|
|
43
|
+
section.split(needle).length - 1;
|
|
44
|
+
expect(occurrences('Keep the session task list current')).toBe(1);
|
|
45
|
+
expect(occurrences('Run independent pieces of work in parallel')).toBe(1);
|
|
46
|
+
expect(
|
|
47
|
+
occurrences(
|
|
48
|
+
'Keep a monitor in place that notices when a sub-agent has produced no output for about 5 minutes.',
|
|
49
|
+
),
|
|
50
|
+
).toBe(1);
|
|
51
|
+
expect(occurrences('share it through a new owner-call')).toBe(1);
|
|
61
52
|
});
|
|
62
53
|
|
|
63
|
-
it('
|
|
54
|
+
it('requests a remaining-minutes estimate in the next output', () => {
|
|
64
55
|
const section = composer.composeMainStalledSection(600);
|
|
65
56
|
expect(section).toContain(
|
|
66
|
-
'
|
|
67
|
-
);
|
|
68
|
-
expect(section).toContain(
|
|
69
|
-
'Please avoid telling the owner to scroll up, go back, or read previous or above messages; if context is needed, restate it inside the owner-call message itself.',
|
|
57
|
+
'an estimate of the remaining minutes to finish all tasks',
|
|
70
58
|
);
|
|
71
59
|
});
|
|
72
60
|
|
|
73
|
-
it('
|
|
74
|
-
const section = composer.composeMainStalledSection(
|
|
75
|
-
expect(section).toContain(
|
|
76
|
-
'If you have COMPLETED or ANSWERED a request from the owner in this session, please fire the owner-call to report the RESULT to the owner',
|
|
77
|
-
);
|
|
78
|
-
expect(section).toContain(
|
|
79
|
-
"completing or answering an owner's requested action is itself a reason to fire the owner-call",
|
|
80
|
-
);
|
|
61
|
+
it('floors the silent seconds to whole minutes', () => {
|
|
62
|
+
const section = composer.composeMainStalledSection(659);
|
|
63
|
+
expect(section).toContain('No output has been observed for 10 minutes.');
|
|
81
64
|
});
|
|
82
65
|
|
|
83
|
-
it('
|
|
66
|
+
it('states the owner-call tag format exactly once: complete pair on one line, content starting with the red-circle emoji, self-contained', () => {
|
|
84
67
|
const section = composer.composeMainStalledSection(600);
|
|
68
|
+
const formatOccurrences =
|
|
69
|
+
section.split('complete opening and closing pair on one line').length - 1;
|
|
70
|
+
expect(formatOccurrences).toBe(1);
|
|
71
|
+
expect(section).toContain('🔴');
|
|
85
72
|
expect(section).toContain(
|
|
86
|
-
'
|
|
87
|
-
);
|
|
88
|
-
expect(section).toContain(
|
|
89
|
-
"the owner's app only surfaces this session when the owner-call fires",
|
|
73
|
+
'with the content starting immediately with the 🔴 emoji',
|
|
90
74
|
);
|
|
75
|
+
expect(section).toContain('written to be self-contained');
|
|
91
76
|
});
|
|
92
77
|
|
|
93
|
-
it('
|
|
78
|
+
it('explains that the owner is notified only when an owner-call is raised', () => {
|
|
94
79
|
const section = composer.composeMainStalledSection(600);
|
|
95
|
-
expect(section).toContain(
|
|
96
|
-
'If this self-check reminder keeps arriving, it is likely because an owner request was completed or answered without firing the owner-call; fire the owner-call now to report the result to the owner.',
|
|
97
|
-
);
|
|
80
|
+
expect(section).toContain('the owner is notified only when one is raised');
|
|
98
81
|
});
|
|
99
82
|
|
|
100
83
|
it('interpolates the configured owner-call marker into the format guidance when provided', () => {
|
|
@@ -103,7 +86,7 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
103
86
|
);
|
|
104
87
|
const section = markedComposer.composeMainStalledSection(600);
|
|
105
88
|
expect(section).toContain(
|
|
106
|
-
'
|
|
89
|
+
'owner-call marker tag "<<OWNER_CALL>>" as a complete opening and closing pair on one line',
|
|
107
90
|
);
|
|
108
91
|
expect(section).toContain('🔴');
|
|
109
92
|
});
|
|
@@ -111,7 +94,7 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
111
94
|
it('falls back to generic owner-call format guidance when no marker is configured', () => {
|
|
112
95
|
const section = composer.composeMainStalledSection(600);
|
|
113
96
|
expect(section).toContain(
|
|
114
|
-
'
|
|
97
|
+
'owner-call marker tag as a complete opening and closing pair on one line',
|
|
115
98
|
);
|
|
116
99
|
expect(section).not.toContain('""');
|
|
117
100
|
});
|
|
@@ -130,43 +113,63 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
130
113
|
3659,
|
|
131
114
|
);
|
|
132
115
|
expect(section).toContain(
|
|
133
|
-
'
|
|
116
|
+
'No output has been observed for 10 minutes, and the owner call raised 60 minutes ago has not yet been acknowledged by the owner.',
|
|
134
117
|
);
|
|
135
118
|
});
|
|
136
119
|
|
|
137
|
-
it('
|
|
120
|
+
it('instructs unconditionally to re-raise the content as a fresh self-contained owner call', () => {
|
|
138
121
|
const section = composer.composeMainStalledWithStaleOwnerCallSection(
|
|
139
122
|
600,
|
|
140
123
|
3600,
|
|
141
124
|
);
|
|
142
125
|
expect(section).toContain(
|
|
143
|
-
'
|
|
144
|
-
);
|
|
145
|
-
expect(section).toContain(
|
|
146
|
-
'Make it fully self-contained: restate the whole situation — what happened, what you are asking, and any decision needed — inside the new owner call itself',
|
|
126
|
+
'Please re-raise its content as a fresh, self-contained owner call.',
|
|
147
127
|
);
|
|
148
128
|
});
|
|
149
129
|
|
|
150
|
-
it('
|
|
130
|
+
it('frames every unanswered owner call kind as awaiting acknowledgment', () => {
|
|
151
131
|
const section = composer.composeMainStalledWithStaleOwnerCallSection(
|
|
152
132
|
600,
|
|
153
133
|
3600,
|
|
154
134
|
);
|
|
135
|
+
expect(section).toContain('acknowledg');
|
|
155
136
|
expect(section).toContain(
|
|
156
|
-
'
|
|
137
|
+
'a completion report, a question, or a decision request',
|
|
138
|
+
);
|
|
139
|
+
expect(section).toContain("still awaiting the owner's acknowledgment");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('contains no not-blocked escape phrasing and no conditional re-raise phrasing', () => {
|
|
143
|
+
const section = composer.composeMainStalledWithStaleOwnerCallSection(
|
|
144
|
+
600,
|
|
145
|
+
3600,
|
|
157
146
|
);
|
|
147
|
+
expect(section).not.toContain('no longer blocked');
|
|
148
|
+
expect(section).not.toContain('continue with the next step');
|
|
149
|
+
expect(section).not.toContain('If you are still waiting');
|
|
150
|
+
expect(section).not.toContain('If you are');
|
|
158
151
|
});
|
|
159
152
|
|
|
160
|
-
it('
|
|
153
|
+
it('requests a remaining-minutes estimate in the stale-owner-call section', () => {
|
|
161
154
|
const section = composer.composeMainStalledWithStaleOwnerCallSection(
|
|
162
155
|
600,
|
|
163
156
|
3600,
|
|
164
157
|
);
|
|
165
158
|
expect(section).toContain(
|
|
166
|
-
'
|
|
159
|
+
'an estimate of the remaining minutes to finish all tasks',
|
|
167
160
|
);
|
|
168
161
|
});
|
|
169
162
|
|
|
163
|
+
it('states the owner-call format guidance exactly once in the stale-owner-call section', () => {
|
|
164
|
+
const section = composer.composeMainStalledWithStaleOwnerCallSection(
|
|
165
|
+
600,
|
|
166
|
+
3600,
|
|
167
|
+
);
|
|
168
|
+
const formatOccurrences =
|
|
169
|
+
section.split('complete opening and closing pair on one line').length - 1;
|
|
170
|
+
expect(formatOccurrences).toBe(1);
|
|
171
|
+
});
|
|
172
|
+
|
|
170
173
|
it('interpolates the configured owner-call marker into the stale-owner-call format guidance', () => {
|
|
171
174
|
const markedComposer = new DefaultSilentSessionMessageComposer(
|
|
172
175
|
'<<OWNER_CALL>>',
|
|
@@ -176,7 +179,7 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
176
179
|
3600,
|
|
177
180
|
);
|
|
178
181
|
expect(section).toContain(
|
|
179
|
-
'
|
|
182
|
+
'owner-call marker tag "<<OWNER_CALL>>" as a complete opening and closing pair on one line',
|
|
180
183
|
);
|
|
181
184
|
expect(section).toContain('🔴');
|
|
182
185
|
});
|
|
@@ -188,7 +191,7 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
188
191
|
3600,
|
|
189
192
|
);
|
|
190
193
|
expect(staleSection).not.toBe(plainSection);
|
|
191
|
-
expect(plainSection).not.toContain('
|
|
194
|
+
expect(plainSection).not.toContain('has not yet been acknowledged');
|
|
192
195
|
});
|
|
193
196
|
|
|
194
197
|
it('emits a distinct idle message for a sub-agent that is only output-idle', () => {
|
|
@@ -205,12 +208,12 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
205
208
|
});
|
|
206
209
|
expect(section).toContain('sub-process-idle');
|
|
207
210
|
expect(section).toContain('no output for 6m');
|
|
208
|
-
expect(section).toContain('restart, hand off, or replace it');
|
|
211
|
+
expect(section).toContain('please restart it, hand it off, or replace it');
|
|
209
212
|
expect(section).toContain('waiting on an external dependency');
|
|
210
|
-
expect(section).not.toContain('
|
|
213
|
+
expect(section).not.toContain('running longer than a task usually takes');
|
|
211
214
|
});
|
|
212
215
|
|
|
213
|
-
it('
|
|
216
|
+
it('frames the idle message as an automated status check with the system-measured duration', () => {
|
|
214
217
|
const section = composer.composeSubAgentSection({
|
|
215
218
|
idleSubAgents: [
|
|
216
219
|
{
|
|
@@ -222,36 +225,14 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
222
225
|
],
|
|
223
226
|
longRunningSubAgents: [],
|
|
224
227
|
});
|
|
228
|
+
expect(section).toContain('This is an automated status check.');
|
|
225
229
|
expect(section).toContain(
|
|
226
|
-
|
|
227
|
-
);
|
|
228
|
-
expect(section).toContain(
|
|
229
|
-
'Please treat that figure as the authoritative system signal; there is no need to spend effort re-deriving whether the sub-process is alive.',
|
|
230
|
+
'produced no output for about the minutes shown, measured from their last tool activity',
|
|
230
231
|
);
|
|
231
232
|
expect(section).toContain('no output for 6m');
|
|
232
233
|
});
|
|
233
234
|
|
|
234
|
-
it('
|
|
235
|
-
const section = composer.composeSubAgentSection({
|
|
236
|
-
idleSubAgents: [
|
|
237
|
-
{
|
|
238
|
-
label: 'sub-process-idle',
|
|
239
|
-
silentSeconds: 360,
|
|
240
|
-
runningSeconds: 60,
|
|
241
|
-
waitingOnExternalProcess: false,
|
|
242
|
-
},
|
|
243
|
-
],
|
|
244
|
-
longRunningSubAgents: [],
|
|
245
|
-
});
|
|
246
|
-
expect(section).toContain(
|
|
247
|
-
'Around five minutes of silence is a real warning of a possible hang',
|
|
248
|
-
);
|
|
249
|
-
expect(section).toContain(
|
|
250
|
-
'please base your assessment on concrete evidence rather than speculation ("probably still working"), and please keep the warning in view until evidence resolves it.',
|
|
251
|
-
);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('requires a concrete cause-check covering recent activity and external-dependency waiting in the idle message', () => {
|
|
235
|
+
it('tells the agent that logging one line suffices for a legitimate external wait in the idle message', () => {
|
|
255
236
|
const section = composer.composeSubAgentSection({
|
|
256
237
|
idleSubAgents: [
|
|
257
238
|
{
|
|
@@ -263,35 +244,11 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
263
244
|
],
|
|
264
245
|
longRunningSubAgents: [],
|
|
265
246
|
});
|
|
266
|
-
expect(section).toContain('determine the CAUSE by a concrete check');
|
|
267
247
|
expect(section).toContain(
|
|
268
|
-
'a
|
|
248
|
+
'a continuous-integration run, an external API, or another process',
|
|
269
249
|
);
|
|
270
250
|
expect(section).toContain(
|
|
271
|
-
'
|
|
272
|
-
);
|
|
273
|
-
expect(section).toContain(
|
|
274
|
-
'confirm it by investigation before concluding — do not assume',
|
|
275
|
-
);
|
|
276
|
-
expect(section).toContain(
|
|
277
|
-
'if it is legitimately waiting, state that conclusion together with the concrete evidence you found',
|
|
278
|
-
);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('requires logging the investigation result even though owner notification is not required', () => {
|
|
282
|
-
const section = composer.composeSubAgentSection({
|
|
283
|
-
idleSubAgents: [
|
|
284
|
-
{
|
|
285
|
-
label: 'sub-process-idle',
|
|
286
|
-
silentSeconds: 360,
|
|
287
|
-
runningSeconds: 60,
|
|
288
|
-
waitingOnExternalProcess: false,
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
longRunningSubAgents: [],
|
|
292
|
-
});
|
|
293
|
-
expect(section).toContain(
|
|
294
|
-
'Owner notification is not required, but please output your investigation result in this session so it remains as a log.',
|
|
251
|
+
'no action is needed — please log one line noting the wait',
|
|
295
252
|
);
|
|
296
253
|
});
|
|
297
254
|
|
|
@@ -309,14 +266,12 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
309
266
|
});
|
|
310
267
|
expect(section).toContain('sub-process-long');
|
|
311
268
|
expect(section).toContain('running for 20m');
|
|
312
|
-
expect(section).toContain('
|
|
313
|
-
expect(section).toContain('too large');
|
|
314
|
-
expect(section).toContain('not making forward progress');
|
|
269
|
+
expect(section).toContain('running longer than a task usually takes');
|
|
315
270
|
expect(section).toContain(
|
|
316
|
-
'
|
|
271
|
+
'breaking the task down, restarting it, handing it off, or replacing it',
|
|
317
272
|
);
|
|
318
273
|
expect(section).toContain(
|
|
319
|
-
'
|
|
274
|
+
'If it is progressing normally, no action is needed.',
|
|
320
275
|
);
|
|
321
276
|
});
|
|
322
277
|
|
|
@@ -348,9 +303,8 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
348
303
|
longRunningSubAgents: [subAgent],
|
|
349
304
|
});
|
|
350
305
|
expect(section).toContain('no output for 6m');
|
|
351
|
-
expect(section).toContain('determine the CAUSE by a concrete check');
|
|
352
306
|
expect(section).toContain('running for 20m');
|
|
353
|
-
expect(section).toContain('
|
|
307
|
+
expect(section).toContain('running longer than a task usually takes');
|
|
354
308
|
const sentinelOccurrences =
|
|
355
309
|
section.split(SILENT_SESSION_REMINDER_SENTINEL).length - 1;
|
|
356
310
|
expect(sentinelOccurrences).toBe(2);
|
|
@@ -382,12 +336,19 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
382
336
|
expect(longRunningSection).not.toContain('idle-only');
|
|
383
337
|
});
|
|
384
338
|
|
|
385
|
-
it('composes default texts free of prohibition-styled vocabulary that trips model safety classifiers', () => {
|
|
339
|
+
it('composes default texts free of pressure- and prohibition-styled vocabulary that trips model safety classifiers', () => {
|
|
386
340
|
const flaggedPatterns = [
|
|
387
341
|
/unacceptable/i,
|
|
388
342
|
/\bnever\b/i,
|
|
389
343
|
/\b[Dd]o NOT\b/,
|
|
390
344
|
/\b[Yy]ou MUST\b/,
|
|
345
|
+
/\bwastes?\b/i,
|
|
346
|
+
/silently stalls/i,
|
|
347
|
+
/\bstall(s|ed|ing)?\b/i,
|
|
348
|
+
/raise one now/i,
|
|
349
|
+
/fire the owner-call now/i,
|
|
350
|
+
/do not wait passively/i,
|
|
351
|
+
/is prohibited/i,
|
|
391
352
|
];
|
|
392
353
|
const markedComposer = new DefaultSilentSessionMessageComposer(
|
|
393
354
|
'<<OWNER_CALL>>',
|
|
@@ -20,9 +20,9 @@ const composeIdleSubAgentSection = (
|
|
|
20
20
|
)}`,
|
|
21
21
|
);
|
|
22
22
|
return [
|
|
23
|
-
`${SILENT_SESSION_REMINDER_SENTINEL}
|
|
23
|
+
`${SILENT_SESSION_REMINDER_SENTINEL} This is an automated status check. The following sub-process(es) have produced no output for about the minutes shown, measured from their last tool activity:`,
|
|
24
24
|
...lines,
|
|
25
|
-
'For each one
|
|
25
|
+
'For each one: if it is waiting on an external dependency (a continuous-integration run, an external API, or another process), no action is needed — please log one line noting the wait. If it appears stuck, please restart it, hand it off, or replace it.',
|
|
26
26
|
].join('\n');
|
|
27
27
|
};
|
|
28
28
|
|
|
@@ -36,20 +36,20 @@ const composeLongRunningSubAgentSection = (
|
|
|
36
36
|
)}`,
|
|
37
37
|
);
|
|
38
38
|
return [
|
|
39
|
-
`${SILENT_SESSION_REMINDER_SENTINEL} The following sub-process(es) have been running longer than a task
|
|
39
|
+
`${SILENT_SESSION_REMINDER_SENTINEL} This is an automated status check. The following sub-process(es) have been running longer than a task usually takes:`,
|
|
40
40
|
...lines,
|
|
41
|
-
'
|
|
41
|
+
'If one is not making progress toward completion, consider breaking the task down, restarting it, handing it off, or replacing it. If it is progressing normally, no action is needed.',
|
|
42
42
|
].join('\n');
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
export const composeOwnerCallFormatGuidance = (
|
|
46
46
|
ownerCallMarker: string | null,
|
|
47
47
|
): string => {
|
|
48
|
-
const
|
|
48
|
+
const tagLabel =
|
|
49
49
|
ownerCallMarker !== null && ownerCallMarker.length > 0
|
|
50
|
-
? `
|
|
51
|
-
:
|
|
52
|
-
return
|
|
50
|
+
? ` "${ownerCallMarker}"`
|
|
51
|
+
: '';
|
|
52
|
+
return `Format reminder: write the owner-call marker tag${tagLabel} as a complete opening and closing pair on one line, with the content starting immediately with the 🔴 emoji and written to be self-contained, so the owner can understand the situation, the ask, and any decision needed from that single message.`;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
const composeMainStalledMessage = (
|
|
@@ -58,12 +58,14 @@ const composeMainStalledMessage = (
|
|
|
58
58
|
): string => {
|
|
59
59
|
const minutes = Math.floor(mainSilentSeconds / 60);
|
|
60
60
|
return [
|
|
61
|
-
`${SILENT_SESSION_REMINDER_SENTINEL}
|
|
62
|
-
`1.
|
|
63
|
-
`2.
|
|
64
|
-
`3.
|
|
65
|
-
`4.
|
|
66
|
-
|
|
61
|
+
`${SILENT_SESSION_REMINDER_SENTINEL} This is an automated status check. No output has been observed for ${minutes} minutes. If you are waiting on an external process, no action is needed — please log one line explaining the wait. Otherwise please continue with the next step, with these points in mind:`,
|
|
62
|
+
`1. Keep the session task list current, marking finished items as done.`,
|
|
63
|
+
`2. Run independent pieces of work in parallel across sub-agents.`,
|
|
64
|
+
`3. Keep a monitor in place that notices when a sub-agent has produced no output for about 5 minutes.`,
|
|
65
|
+
`4. When an owner decision is needed, or when an owner request has been completed or answered, please share it through a new owner-call — the owner is notified only when one is raised. ${composeOwnerCallFormatGuidance(
|
|
66
|
+
ownerCallMarker,
|
|
67
|
+
)}`,
|
|
68
|
+
`Please also include in your next output an estimate of the remaining minutes to finish all tasks.`,
|
|
67
69
|
].join('\n');
|
|
68
70
|
};
|
|
69
71
|
|
|
@@ -75,10 +77,11 @@ const composeMainStalledWithStaleOwnerCallMessage = (
|
|
|
75
77
|
const silentMinutes = Math.floor(mainSilentSeconds / 60);
|
|
76
78
|
const ownerCallAgeMinutes = Math.floor(unansweredOwnerCallAgeSeconds / 60);
|
|
77
79
|
return [
|
|
78
|
-
`${SILENT_SESSION_REMINDER_SENTINEL}
|
|
79
|
-
`
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
`${SILENT_SESSION_REMINDER_SENTINEL} This is an automated status check. No output has been observed for ${silentMinutes} minutes, and the owner call raised ${ownerCallAgeMinutes} minutes ago has not yet been acknowledged by the owner.`,
|
|
81
|
+
`An owner call without an acknowledgment — whether it carried a completion report, a question, or a decision request — is still awaiting the owner's acknowledgment, and the earlier message may have been missed. Please re-raise its content as a fresh, self-contained owner call. ${composeOwnerCallFormatGuidance(
|
|
82
|
+
ownerCallMarker,
|
|
83
|
+
)}`,
|
|
84
|
+
`Please also include in your next output an estimate of the remaining minutes to finish all tasks.`,
|
|
82
85
|
].join('\n');
|
|
83
86
|
};
|
|
84
87
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultSilentSessionMessageComposer.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/DefaultSilentSessionMessageComposer.ts"],"names":[],"mappings":"AACA,OAAO,EACL,4BAA4B,EAC5B,qBAAqB,EACtB,MAAM,mDAAmD,CAAC;AAwC3D,eAAO,MAAM,8BAA8B,GACzC,iBAAiB,MAAM,GAAG,IAAI,KAC7B,MAMF,CAAC;
|
|
1
|
+
{"version":3,"file":"DefaultSilentSessionMessageComposer.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/DefaultSilentSessionMessageComposer.ts"],"names":[],"mappings":"AACA,OAAO,EACL,4BAA4B,EAC5B,qBAAqB,EACtB,MAAM,mDAAmD,CAAC;AAwC3D,eAAO,MAAM,8BAA8B,GACzC,iBAAiB,MAAM,GAAG,IAAI,KAC7B,MAMF,CAAC;AAmCF,qBAAa,mCAAoC,YAAW,4BAA4B;IAC1E,OAAO,CAAC,QAAQ,CAAC,eAAe;gBAAf,eAAe,GAAE,MAAM,GAAG,IAAW;IAElE,yBAAyB,GAAI,mBAAmB,MAAM,KAAG,MAAM,CAE7D;IAEF,2CAA2C,GACzC,mBAAmB,MAAM,EACzB,+BAA+B,MAAM,KACpC,MAAM,CAMP;IAEF,sBAAsB,GAAI,eAAe,qBAAqB,KAAG,MAAM,CAWrE;CACH"}
|