github-issue-tower-defence-management 1.122.5 → 1.122.7
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 +3 -3
- package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.test.ts +4 -9
- package/src/domain/usecases/DefaultSilentSessionMessageComposer.test.ts +91 -127
- 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.7](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.122.6...v1.122.7) (2026-07-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **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))
|
|
7
|
+
|
|
8
|
+
## [1.122.6](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.122.5...v1.122.6) (2026-07-13)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **silent-monitor:** neutralize imperative reminder wording that trips model safety classifiers ([#1164](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1164)) ([e82e704](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/e82e70438b3e6be00d20d5a3e5037c6bb7ea0325))
|
|
14
|
+
|
|
1
15
|
## [1.122.5](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.122.4...v1.122.5) (2026-07-12)
|
|
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 is still unanswered.`,
|
|
48
|
+
`If you are still waiting on that answer, the older owner call may have been missed, so please re-raise the ask as a fresh, self-contained owner call. ${(0, exports.composeOwnerCallFormatGuidance)(ownerCallMarker)}`,
|
|
49
|
+
`If you are no longer blocked, please continue with the next step. 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
|
|
|
@@ -310,7 +310,7 @@ describe('notifySilentTmuxSessions', () => {
|
|
|
310
310
|
expect(sendCall?.[1][2]).toBe(SESSION_NAME);
|
|
311
311
|
expect(sendCall?.[1][4]).toContain('is still unanswered');
|
|
312
312
|
expect(sendCall?.[1][4]).toContain(
|
|
313
|
-
're-raise
|
|
313
|
+
're-raise the ask as a fresh, self-contained owner call',
|
|
314
314
|
);
|
|
315
315
|
});
|
|
316
316
|
|
|
@@ -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 MUST 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
|
-
'NEVER tell 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
|
-
'NEVER tell 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, you MUST 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,40 +113,30 @@ 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 is still unanswered.',
|
|
134
117
|
);
|
|
135
118
|
});
|
|
136
119
|
|
|
137
|
-
it('
|
|
120
|
+
it('suggests re-raising the pending ask as a fresh self-contained owner call when still waiting', () => {
|
|
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 the ask as a fresh, self-contained owner call',
|
|
147
127
|
);
|
|
148
128
|
});
|
|
149
129
|
|
|
150
|
-
it('
|
|
130
|
+
it('suggests continuing when no longer blocked and requests a remaining-minutes estimate', () => {
|
|
151
131
|
const section = composer.composeMainStalledWithStaleOwnerCallSection(
|
|
152
132
|
600,
|
|
153
133
|
3600,
|
|
154
134
|
);
|
|
155
135
|
expect(section).toContain(
|
|
156
|
-
'If you are no longer blocked
|
|
157
|
-
);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('states that the stale-owner-call reminder itself does not notify the owner', () => {
|
|
161
|
-
const section = composer.composeMainStalledWithStaleOwnerCallSection(
|
|
162
|
-
600,
|
|
163
|
-
3600,
|
|
136
|
+
'If you are no longer blocked, please continue with the next step.',
|
|
164
137
|
);
|
|
165
138
|
expect(section).toContain(
|
|
166
|
-
'
|
|
139
|
+
'an estimate of the remaining minutes to finish all tasks',
|
|
167
140
|
);
|
|
168
141
|
});
|
|
169
142
|
|
|
@@ -176,7 +149,7 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
176
149
|
3600,
|
|
177
150
|
);
|
|
178
151
|
expect(section).toContain(
|
|
179
|
-
'
|
|
152
|
+
'owner-call marker tag "<<OWNER_CALL>>" as a complete opening and closing pair on one line',
|
|
180
153
|
);
|
|
181
154
|
expect(section).toContain('🔴');
|
|
182
155
|
});
|
|
@@ -205,12 +178,12 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
205
178
|
});
|
|
206
179
|
expect(section).toContain('sub-process-idle');
|
|
207
180
|
expect(section).toContain('no output for 6m');
|
|
208
|
-
expect(section).toContain('restart, hand off, or replace it');
|
|
181
|
+
expect(section).toContain('please restart it, hand it off, or replace it');
|
|
209
182
|
expect(section).toContain('waiting on an external dependency');
|
|
210
|
-
expect(section).not.toContain('
|
|
183
|
+
expect(section).not.toContain('running longer than a task usually takes');
|
|
211
184
|
});
|
|
212
185
|
|
|
213
|
-
it('
|
|
186
|
+
it('frames the idle message as an automated status check with the system-measured duration', () => {
|
|
214
187
|
const section = composer.composeSubAgentSection({
|
|
215
188
|
idleSubAgents: [
|
|
216
189
|
{
|
|
@@ -222,36 +195,14 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
222
195
|
],
|
|
223
196
|
longRunningSubAgents: [],
|
|
224
197
|
});
|
|
198
|
+
expect(section).toContain('This is an automated status check.');
|
|
225
199
|
expect(section).toContain(
|
|
226
|
-
|
|
227
|
-
);
|
|
228
|
-
expect(section).toContain(
|
|
229
|
-
'Treat that figure as the authoritative system signal; do NOT spend effort re-deriving whether the sub-process is alive.',
|
|
200
|
+
'produced no output for about the minutes shown, measured from their last tool activity',
|
|
230
201
|
);
|
|
231
202
|
expect(section).toContain('no output for 6m');
|
|
232
203
|
});
|
|
233
204
|
|
|
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
|
-
'do NOT speak from speculation ("probably still working") and do NOT dismiss it without evidence.',
|
|
251
|
-
);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('requires a concrete cause-check covering recent activity and external-dependency waiting in the idle message', () => {
|
|
205
|
+
it('tells the agent that logging one line suffices for a legitimate external wait in the idle message', () => {
|
|
255
206
|
const section = composer.composeSubAgentSection({
|
|
256
207
|
idleSubAgents: [
|
|
257
208
|
{
|
|
@@ -263,35 +214,11 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
263
214
|
],
|
|
264
215
|
longRunningSubAgents: [],
|
|
265
216
|
});
|
|
266
|
-
expect(section).toContain('determine the CAUSE by a concrete check');
|
|
267
|
-
expect(section).toContain(
|
|
268
|
-
'a very recent push or commit, or output from any nested sub-processes this sub-process itself started',
|
|
269
|
-
);
|
|
270
|
-
expect(section).toContain(
|
|
271
|
-
'legitimately blocked waiting on an external dependency',
|
|
272
|
-
);
|
|
273
217
|
expect(section).toContain(
|
|
274
|
-
'
|
|
218
|
+
'a continuous-integration run, an external API, or another process',
|
|
275
219
|
);
|
|
276
220
|
expect(section).toContain(
|
|
277
|
-
'
|
|
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 you MUST output your investigation result in this session so it remains as a log.',
|
|
221
|
+
'no action is needed — please log one line noting the wait',
|
|
295
222
|
);
|
|
296
223
|
});
|
|
297
224
|
|
|
@@ -309,14 +236,12 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
309
236
|
});
|
|
310
237
|
expect(section).toContain('sub-process-long');
|
|
311
238
|
expect(section).toContain('running for 20m');
|
|
312
|
-
expect(section).toContain('
|
|
313
|
-
expect(section).toContain('too large');
|
|
314
|
-
expect(section).toContain('not making forward progress');
|
|
239
|
+
expect(section).toContain('running longer than a task usually takes');
|
|
315
240
|
expect(section).toContain(
|
|
316
|
-
'
|
|
241
|
+
'breaking the task down, restarting it, handing it off, or replacing it',
|
|
317
242
|
);
|
|
318
243
|
expect(section).toContain(
|
|
319
|
-
'
|
|
244
|
+
'If it is progressing normally, no action is needed.',
|
|
320
245
|
);
|
|
321
246
|
});
|
|
322
247
|
|
|
@@ -348,9 +273,8 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
348
273
|
longRunningSubAgents: [subAgent],
|
|
349
274
|
});
|
|
350
275
|
expect(section).toContain('no output for 6m');
|
|
351
|
-
expect(section).toContain('determine the CAUSE by a concrete check');
|
|
352
276
|
expect(section).toContain('running for 20m');
|
|
353
|
-
expect(section).toContain('
|
|
277
|
+
expect(section).toContain('running longer than a task usually takes');
|
|
354
278
|
const sentinelOccurrences =
|
|
355
279
|
section.split(SILENT_SESSION_REMINDER_SENTINEL).length - 1;
|
|
356
280
|
expect(sentinelOccurrences).toBe(2);
|
|
@@ -382,6 +306,46 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
382
306
|
expect(longRunningSection).not.toContain('idle-only');
|
|
383
307
|
});
|
|
384
308
|
|
|
309
|
+
it('composes default texts free of pressure- and prohibition-styled vocabulary that trips model safety classifiers', () => {
|
|
310
|
+
const flaggedPatterns = [
|
|
311
|
+
/unacceptable/i,
|
|
312
|
+
/\bnever\b/i,
|
|
313
|
+
/\b[Dd]o NOT\b/,
|
|
314
|
+
/\b[Yy]ou MUST\b/,
|
|
315
|
+
/\bwastes?\b/i,
|
|
316
|
+
/silently stalls/i,
|
|
317
|
+
/\bstall(s|ed|ing)?\b/i,
|
|
318
|
+
/raise one now/i,
|
|
319
|
+
/fire the owner-call now/i,
|
|
320
|
+
/do not wait passively/i,
|
|
321
|
+
/is prohibited/i,
|
|
322
|
+
];
|
|
323
|
+
const markedComposer = new DefaultSilentSessionMessageComposer(
|
|
324
|
+
'<<OWNER_CALL>>',
|
|
325
|
+
);
|
|
326
|
+
const subAgent = {
|
|
327
|
+
label: 'sub-process-1',
|
|
328
|
+
silentSeconds: 360,
|
|
329
|
+
runningSeconds: 1200,
|
|
330
|
+
waitingOnExternalProcess: false,
|
|
331
|
+
};
|
|
332
|
+
const composedDefaultTexts = [
|
|
333
|
+
composer.composeMainStalledSection(600),
|
|
334
|
+
markedComposer.composeMainStalledSection(600),
|
|
335
|
+
composer.composeMainStalledWithStaleOwnerCallSection(600, 3600),
|
|
336
|
+
markedComposer.composeMainStalledWithStaleOwnerCallSection(600, 3600),
|
|
337
|
+
composer.composeSubAgentSection({
|
|
338
|
+
idleSubAgents: [subAgent],
|
|
339
|
+
longRunningSubAgents: [subAgent],
|
|
340
|
+
}),
|
|
341
|
+
];
|
|
342
|
+
for (const text of composedDefaultTexts) {
|
|
343
|
+
for (const pattern of flaggedPatterns) {
|
|
344
|
+
expect(text).not.toMatch(pattern);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
385
349
|
it('does not contain any host-specific or internal identifiers', () => {
|
|
386
350
|
const mainSection = composer.composeMainStalledSection(600);
|
|
387
351
|
const subAgent = {
|
|
@@ -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 is still unanswered.`,
|
|
81
|
+
`If you are still waiting on that answer, the older owner call may have been missed, so please re-raise the ask as a fresh, self-contained owner call. ${composeOwnerCallFormatGuidance(
|
|
82
|
+
ownerCallMarker,
|
|
83
|
+
)}`,
|
|
84
|
+
`If you are no longer blocked, please continue with the next step. 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"}
|