feat(core): implement Narrative Progress Protocol via update_topic tool

- Rename `create_new_topic` to `update_topic` to support continuous narrative heartbeats.
- Introduce `strategic_intent` parameter to capture tactical reasoning within tool-use turns.
- Consolidate `previous_summary` and `current_summary` into a single `summary` parameter to simplify model narrative bridging.
- Implement Smart Narrative Routing in `UpdateTopicTool` to dynamically toggle between full chapter headers and tactical intent-only heartbeats based on topic transitions.
- Update `TopicState` to track both active topic and active intent session-wide.
- Overhaul system prompt snippets to mandate the Heartbeat protocol, enforcing strictly granular, single-idea chapters and forbidding clubbed phases.
- Gate all logic and tool accessibility behind the `topicUpdateNarration` experimental flag.
- Update security policies (read-only and plan) to whitelist the heartbeat tool.
- Refactor scheduler logic to prioritize `update_topic` as the first execution in any batch.
- Synchronize all core and CLI configuration tests with the new tool schema and naming.
This commit is contained in:
Abhijit Balaji
2026-03-23 12:12:49 -07:00
parent 72e61d1c6f
commit 7bf08065f7
19 changed files with 237 additions and 215 deletions
+8 -10
View File
@@ -15,7 +15,7 @@ import {
EDIT_TOOL_NAME,
WEB_FETCH_TOOL_NAME,
ASK_USER_TOOL_NAME,
CREATE_NEW_TOPIC_TOOL_NAME,
UPDATE_TOPIC_TOOL_NAME,
type ExtensionLoader,
debugLogger,
ApprovalMode,
@@ -1091,7 +1091,7 @@ describe('mergeExcludeTools', () => {
'tool3',
'tool4',
'tool5',
CREATE_NEW_TOPIC_TOOL_NAME,
UPDATE_TOPIC_TOOL_NAME,
]),
);
expect(config.getExcludeTools()).toHaveLength(6);
@@ -1116,7 +1116,7 @@ describe('mergeExcludeTools', () => {
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getExcludeTools()).toEqual(
new Set(['tool1', 'tool2', 'tool3', CREATE_NEW_TOPIC_TOOL_NAME]),
new Set(['tool1', 'tool2', 'tool3', UPDATE_TOPIC_TOOL_NAME]),
);
expect(config.getExcludeTools()).toHaveLength(4);
});
@@ -1149,7 +1149,7 @@ describe('mergeExcludeTools', () => {
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getExcludeTools()).toEqual(
new Set(['tool1', 'tool2', 'tool3', 'tool4', CREATE_NEW_TOPIC_TOOL_NAME]),
new Set(['tool1', 'tool2', 'tool3', 'tool4', UPDATE_TOPIC_TOOL_NAME]),
);
expect(config.getExcludeTools()).toHaveLength(5);
});
@@ -1160,9 +1160,7 @@ describe('mergeExcludeTools', () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getExcludeTools()).toEqual(
new Set([CREATE_NEW_TOPIC_TOOL_NAME]),
);
expect(config.getExcludeTools()).toEqual(new Set([UPDATE_TOPIC_TOOL_NAME]));
});
it('should return default excludes when no excludeTools are specified and it is not interactive', async () => {
@@ -1172,7 +1170,7 @@ describe('mergeExcludeTools', () => {
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getExcludeTools()).toEqual(
new Set([ASK_USER_TOOL_NAME, CREATE_NEW_TOPIC_TOOL_NAME]),
new Set([ASK_USER_TOOL_NAME, UPDATE_TOPIC_TOOL_NAME]),
);
});
@@ -1185,7 +1183,7 @@ describe('mergeExcludeTools', () => {
vi.spyOn(ExtensionManager.prototype, 'getExtensions').mockReturnValue([]);
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getExcludeTools()).toEqual(
new Set(['tool1', 'tool2', CREATE_NEW_TOPIC_TOOL_NAME]),
new Set(['tool1', 'tool2', UPDATE_TOPIC_TOOL_NAME]),
);
expect(config.getExcludeTools()).toHaveLength(3);
});
@@ -1207,7 +1205,7 @@ describe('mergeExcludeTools', () => {
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getExcludeTools()).toEqual(
new Set(['tool1', 'tool2', CREATE_NEW_TOPIC_TOOL_NAME]),
new Set(['tool1', 'tool2', UPDATE_TOPIC_TOOL_NAME]),
);
expect(config.getExcludeTools()).toHaveLength(3);
});