@devflow-core/dsh-devflow 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,10 @@
5
5
  * preset's full DevFlow prompt from agent.cordis.yml) is restored untouched,
6
6
  * and the workspace line is only appended when the persona does not already
7
7
  * mention a working directory (the DevFlow template resolves {{cwd}} itself).
8
+ *
9
+ * Synced with dsh-liangshen 0.3.14: goal whitelist (#578), presentation
10
+ * broadcasts (#1128), and the snapshotEvents() session-events fallback for
11
+ * newer DSH releases.
8
12
  */
9
13
 
10
14
  /**
@@ -16,7 +20,8 @@
16
20
  * - prompt sections: only the persona section (all other sections,
17
21
  * including plan-mode's `plan:policy`, return after promotion)
18
22
  * - runtime contexts: emptied (no sandbox/approval snapshot)
19
- * - pre-step messages: only explicit user messages pass
23
+ * - pre-step messages: only whitelisted source kinds pass (direct user
24
+ * messages and goal auto-rounds by default)
20
25
  *
21
26
  * Promotion opens the full tool catalog and restores runtime contexts and all
22
27
  * prompt sections. With `anchorGate` the promotion after the first tool call
@@ -82,11 +87,13 @@ const PERSONA_SECTION_NAMES = new Set(['deployment:persona', 'persona'])
82
87
  */
83
88
  const WORKSPACE_LINE_PREFIX = '\n\nYour working directory is '
84
89
 
85
- /** Message-source kinds the model may see during phase 1. */
86
- const DEFAULT_MESSAGE_SOURCES = ['user']
87
-
88
- /** Message-source kinds delayed after promotion. */
89
- const DEFAULT_DEFERRED_SOURCES = []
90
+ /**
91
+ * Message-source kinds the model may see during phase 1. Goal auto-rounds
92
+ * (source kind `goal`, issue #578) must be here: a filtered-out goal round
93
+ * never produces a response or tool call, so no promotion branch ever fires
94
+ * and the goal resume/pause loop deadlocks.
95
+ */
96
+ const DEFAULT_MESSAGE_SOURCES = ['user', 'goal']
90
97
 
91
98
  function stringList(value, field, fallback) {
92
99
  if (value === undefined) return [...fallback]
@@ -153,12 +160,15 @@ export function hasAnchoredReasoning(content) {
153
160
  }
154
161
 
155
162
  /**
156
- * Whether one pre-step message is an explicit user message. Only `kind:
157
- * 'user'` passes; injected kinds and source-less seed messages never pass.
163
+ * Whether one pre-step message belongs to a whitelisted source kind. The
164
+ * configured whitelist alone decides; injected kinds and source-less seed
165
+ * messages never pass unless explicitly named. (Before issue #578 this also
166
+ * hardcoded `kind === 'user'`, so no whitelist entry could ever admit a
167
+ * goal auto-round and `/goal` sessions deadlocked in phase 1.)
158
168
  */
159
169
  function isAllowedMessage(message, allowedSources) {
160
170
  const kind = message.source?.kind
161
- return kind === 'user' && allowedSources.has(kind)
171
+ return kind !== undefined && allowedSources.has(kind)
162
172
  }
163
173
 
164
174
  /** Whether one pre-step message belongs to a deferred injection kind. */
@@ -276,7 +286,7 @@ function stateFor(session) {
276
286
  * Code Mode presentation is disposed so the next assembly sees the native
277
287
  * catalog and the phase-1 filter can narrow it again.
278
288
  */
279
- function resetToControlled(state) {
289
+ function resetToControlled(state, session) {
280
290
  if (typeof state.presentationDisposer === 'function') {
281
291
  try {
282
292
  state.presentationDisposer()
@@ -285,6 +295,10 @@ function resetToControlled(state) {
285
295
  // next promotion re-declares Code Mode anyway.
286
296
  }
287
297
  state.presentationDisposer = undefined
298
+ const agent = session !== undefined ? agentBySession.get(session) : undefined
299
+ if (agent?.ctx && typeof agent.ctx.emit === 'function') {
300
+ agent.ctx.emit('tools/presentation-changed', { mode: 'native', session: session?.id })
301
+ }
288
302
  }
289
303
  state.promoted = false
290
304
  state.toolCalled = false
@@ -305,13 +319,20 @@ function resetToControlled(state) {
305
319
  */
306
320
  function applyPresentation(agent, state, policy) {
307
321
  if (state.presentationApplied || policy.promotedPresentation !== 'code') return
308
- state.presentationApplied = true
309
- const tools = agent.ctx.tools
322
+ const tools = agent?.ctx?.tools
323
+ // Latch only after the switch really happened: without a tools view there
324
+ // is nothing to present, and latching early would skip Code Mode forever.
310
325
  if (tools === undefined) return
311
326
  // The disposer restores the deployment-default (native) presentation; it is
312
327
  // kept on the state so a post-compaction reset can release Code Mode and
313
328
  // let the phase-1 catalog filter see the native tool list again.
314
329
  state.presentationDisposer = tools.presentAs('code')
330
+ state.presentationApplied = true
331
+ // #1128: Broadcast presentation switch so external discipline / analysis
332
+ // plugins decouple presentation mode from tool failure detection.
333
+ if (typeof agent?.ctx?.emit === 'function') {
334
+ agent.ctx.emit('tools/presentation-changed', { mode: 'code', session: agent.session?.id })
335
+ }
315
336
  }
316
337
 
317
338
  /**
@@ -333,7 +354,11 @@ function decidePromotion(state, config) {
333
354
 
334
355
  /** Scan newly appended session events and update promotion state. */
335
356
  function scanEvents(state, session) {
336
- const events = session.events
357
+ const events = Array.isArray(session?.events)
358
+ ? session.events
359
+ : typeof session?.snapshotEvents === 'function'
360
+ ? session.snapshotEvents()
361
+ : []
337
362
  for (; state.next < events.length; state.next += 1) {
338
363
  const event = events[state.next]
339
364
  if (event === undefined) continue
@@ -343,7 +368,7 @@ function scanEvents(state, session) {
343
368
  // past this boundary (the `next` pointer stays, so events before the
344
369
  // boundary never re-promote). Handled inside the scan so cold starts
345
370
  // reconstruct the same phase from the durable log.
346
- resetToControlled(state)
371
+ resetToControlled(state, session)
347
372
  } else if (event.type === 'tool/call') {
348
373
  state.toolCalled = true
349
374
  } else if (event.type === 'step/start') {
@@ -452,7 +477,7 @@ export function apply(ctx, config) {
452
477
  // start reconstructs the same controlled phase from the durable log.
453
478
  ctx.on('session/event', (session, event) => {
454
479
  if (event.type === 'compaction/end') {
455
- resetToControlled(stateFor(session))
480
+ resetToControlled(stateFor(session), session)
456
481
  return
457
482
  }
458
483
  if (event.type !== 'step/end' && event.type !== 'turn/end') return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devflow-core/dsh-devflow",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "DevFlow for DeepSeek Harness: devflow-2 agent preset + skills + commands + verification scripts, synced into ~/.dsh on host startup.",
5
5
  "type": "module",
6
6
  "engines": {