@danceiny/gotry 0.0.1-rc.15 → 0.0.1-rc.17
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/README.md +51 -13
- package/README.zh-CN.md +26 -12
- package/bin/gotry-booking-copilot.js +53 -0
- package/bin/gotry-bootstrap.js +357 -8
- package/bin/gotry-inner.js +442 -60
- package/bin/gotry-runtime-resolution.d.ts +27 -0
- package/bin/gotry-runtime-resolution.js +50 -0
- package/bin/gotry.js +1 -1
- package/cordis.gotry-patch.yml +15 -1
- package/data/airline-airports.json +39 -0
- package/dist/capabilities/agent-reach-deep.js +1 -1
- package/dist/capabilities/agent-reach.js +1 -1
- package/dist/capabilities/anything.js +1 -1
- package/dist/capabilities/artifacts.js +1 -1
- package/dist/capabilities/effect.js +293 -0
- package/dist/capabilities/fact-log.js +41 -0
- package/dist/capabilities/flyai.js +20 -6
- package/dist/capabilities/hbcli.js +43 -1
- package/dist/capabilities/incident-log.js +1 -1
- package/dist/capabilities/model-override.js +18 -0
- package/dist/capabilities/opensky.js +1 -1
- package/dist/capabilities/resilience.js +90 -0
- package/dist/capabilities/session/action-cache.js +1 -1
- package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
- package/dist/capabilities/session/adapters/meituan-local.js +1 -1
- package/dist/capabilities/session/benchmark.js +2 -1
- package/dist/capabilities/session/extension-bridge.js +358 -0
- package/dist/capabilities/session/extension-channel.js +93 -0
- package/dist/capabilities/session/extension-distribution.js +234 -0
- package/dist/capabilities/session/extract.js +1 -1
- package/dist/capabilities/session/golden-score.js +92 -0
- package/dist/capabilities/session/health-watch.js +216 -0
- package/dist/capabilities/session/read-guard.js +1 -1
- package/dist/capabilities/session/static-flight-golden.js +137 -0
- package/dist/capabilities/session/transport.js +1 -1
- package/dist/capabilities/session/wizard.js +147 -0
- package/dist/capabilities/session-consent.js +1 -1
- package/dist/capabilities/session-login.js +83 -1
- package/dist/capabilities/session-search.js +111 -2
- package/dist/capabilities/weather.js +168 -46
- package/dist/data/session-golden-20.json +25 -0
- package/dist/data/sf-golden-manifest.json +102 -0
- package/dist/data/sf-static-routes.json +91 -0
- package/dist/scripts/action-cache-tests.js +1 -1
- package/dist/scripts/agent-planning-budget-e2e.js +227 -0
- package/dist/scripts/agent-planning-budget-tests.js +173 -0
- package/dist/scripts/agent-reach-deep-tests.js +1 -1
- package/dist/scripts/agent-reach-tests.js +1 -1
- package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
- package/dist/scripts/anything-tests.js +1 -1
- package/dist/scripts/async-collect.js +1 -1
- package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
- package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
- package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
- package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
- package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
- package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
- package/dist/scripts/booking-saga-tests.js +1 -1
- package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
- package/dist/scripts/bootstrap-tests.js +59 -3
- package/dist/scripts/build-changelog.js +279 -0
- package/dist/scripts/changelog-tests.js +165 -0
- package/dist/scripts/companion-tests.js +1 -1
- package/dist/scripts/diff-test.js +1 -1
- package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
- package/dist/scripts/dsh-runtime-closure.js +150 -0
- package/dist/scripts/effect-tests.js +474 -0
- package/dist/scripts/engine-run.js +1 -1
- package/dist/scripts/engine-tests.js +1 -1
- package/dist/scripts/evaluation-cadence-tests.js +347 -0
- package/dist/scripts/evaluation-contract-tests.js +574 -0
- package/dist/scripts/extension-distribution-cli.js +35 -0
- package/dist/scripts/extension-distribution-tests.js +341 -0
- package/dist/scripts/extension-tests.js +573 -0
- package/dist/scripts/fact-gate-tests.js +318 -0
- package/dist/scripts/flyai-tests.js +1 -1
- package/dist/scripts/hbcli-e2e-tests.js +180 -0
- package/dist/scripts/hbcli-tests.js +1 -1
- package/dist/scripts/health-watch-cli.js +51 -0
- package/dist/scripts/i18n-tests.js +1 -1
- package/dist/scripts/incident-tests.js +1 -1
- package/dist/scripts/journey-tests.js +1 -1
- package/dist/scripts/ledger-tests.js +1 -1
- package/dist/scripts/ledger-workflow-crash.js +1 -1
- package/dist/scripts/memory-capture-tests.js +1 -1
- package/dist/scripts/memory-decay-tests.js +1 -1
- package/dist/scripts/memory-metrics.js +1 -1
- package/dist/scripts/memory-value-report.js +1 -1
- package/dist/scripts/model-override-e2e.js +176 -0
- package/dist/scripts/nightly-evidence-tests.js +67 -4
- package/dist/scripts/nightly-evidence.js +59 -3
- package/dist/scripts/nudge-digest.js +1 -1
- package/dist/scripts/onboarding-tests.js +186 -0
- package/dist/scripts/opensky-check.js +1 -1
- package/dist/scripts/opensky-tests.js +1 -1
- package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
- package/dist/scripts/price-drift-tests.js +441 -0
- package/dist/scripts/price-drift-watch.js +491 -0
- package/dist/scripts/probe-poi-tests.js +1 -1
- package/dist/scripts/product-metrics.js +1 -1
- package/dist/scripts/publish-preverify.js +40 -4
- package/dist/scripts/realtime-pricing-tests.js +1 -1
- package/dist/scripts/replay-async.js +1 -1
- package/dist/scripts/replay-real.js +1 -1
- package/dist/scripts/replay.js +1 -1
- package/dist/scripts/session-attach-diagnose.js +1 -1
- package/dist/scripts/session-attach-poc.js +1 -1
- package/dist/scripts/session-benchmark.js +1 -1
- package/dist/scripts/session-extract-tests.js +1 -1
- package/dist/scripts/session-login.js +1 -1
- package/dist/scripts/session-tests.js +70 -20
- package/dist/scripts/sf-live-benchmark.js +338 -0
- package/dist/scripts/sf-live-cli-tests.js +21 -0
- package/dist/scripts/sf-soft-score-tests.js +108 -0
- package/dist/scripts/sf-summary.js +93 -0
- package/dist/scripts/skeleton-check.js +1 -1
- package/dist/scripts/skeleton-integration-test.js +1 -1
- package/dist/scripts/skills-contract-tests.js +1 -1
- package/dist/scripts/smoke-session-gate-tests.js +29 -0
- package/dist/scripts/smoke.js +154 -33
- package/dist/scripts/state-cli-tests.js +1 -1
- package/dist/scripts/state-cli.js +1 -1
- package/dist/scripts/static-golden-tests.js +299 -0
- package/dist/scripts/time-eval-tests.js +1 -1
- package/dist/scripts/travel-timeline-tests.js +1 -1
- package/dist/scripts/unified-tests.js +1 -1
- package/dist/scripts/weather-tests.js +694 -44
- package/dist/scripts/z3-race-tests.js +1 -1
- package/dist/src/artifact-gate.js +310 -0
- package/dist/src/benchmark-agent-conformance.js +370 -0
- package/dist/src/benchmark-environment-bridge.js +384 -0
- package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
- package/dist/src/benchmark-tool-isolation.js +124 -0
- package/dist/src/bookable-facts.js +349 -0
- package/dist/src/booking-saga.js +1 -1
- package/dist/src/booking-surface/availability-policy-v2.js +830 -0
- package/dist/src/booking-surface/canonical-schema.js +113 -0
- package/dist/src/booking-surface/contracts-v2.js +89 -0
- package/dist/src/booking-surface/contracts.js +46 -0
- package/dist/src/booking-surface/dsh-planner.js +453 -0
- package/dist/src/booking-surface/dsh-plugin.js +93 -0
- package/dist/src/booking-surface/error-codes.js +94 -0
- package/dist/src/booking-surface/index.js +15 -0
- package/dist/src/booking-surface/profile.js +68 -0
- package/dist/src/booking-surface/runtime-v2.js +1771 -0
- package/dist/src/booking-surface/runtime.js +351 -0
- package/dist/src/booking-surface/server-v2.js +334 -0
- package/dist/src/booking-surface/server.js +302 -0
- package/dist/src/booking-surface/startup.js +159 -0
- package/dist/src/booking-surface/validation-v2.js +319 -0
- package/dist/src/booking-surface/validation.js +809 -0
- package/dist/src/bridge.js +1 -1
- package/dist/src/companions.js +1 -1
- package/dist/src/contracts.js +1 -1
- package/dist/src/dsh-llm.js +1 -1
- package/dist/src/engine.js +1 -1
- package/dist/src/evaluation-cadence.js +234 -0
- package/dist/src/evaluation-contracts.js +906 -0
- package/dist/src/i18n.js +1 -1
- package/dist/src/index.js +234 -60
- package/dist/src/journey.js +1 -1
- package/dist/src/loop.js +1 -1
- package/dist/src/memory-capture.js +1 -1
- package/dist/src/memory-decay.js +1 -1
- package/dist/src/memory-utility.js +1 -1
- package/dist/src/mock-llm.js +1 -1
- package/dist/src/model.js +1 -1
- package/dist/src/realtime-pricing.js +14 -7
- package/dist/src/slot-spec.js +1 -1
- package/dist/src/state-ledger.js +2 -1
- package/dist/src/time-anchor.js +1 -1
- package/dist/src/tool-budget.js +136 -0
- package/dist/src/tool-packet.js +1 -1
- package/dist/src/travel-slots.js +1 -1
- package/dist/src/travel-timeline.js +1 -1
- package/dist/src/unified.js +1 -1
- package/dist/src/wish-pool.js +1 -1
- package/dist/src/z3-shared.js +1 -1
- package/extension/README.md +50 -0
- package/extension/background.js +160 -0
- package/extension/content-bridge.js +41 -0
- package/extension/content-main.js +62 -0
- package/extension/manifest.json +32 -0
- package/package.json +291 -12
- package/schemas/booking.surface.v1.schema.json +927 -0
- package/schemas/booking.surface.v2.schema.json +61 -0
- package/ts/capabilities/fact-log.ts +54 -0
- package/ts/capabilities/flyai.ts +16 -3
- package/ts/capabilities/hbcli.ts +46 -0
- package/ts/capabilities/session/benchmark.ts +4 -1
- package/ts/capabilities/session/extension-bridge.ts +377 -0
- package/ts/capabilities/session/extension-channel.ts +95 -0
- package/ts/capabilities/session/extension-distribution.ts +264 -0
- package/ts/capabilities/session/golden-score.ts +139 -0
- package/ts/capabilities/session/health-watch.ts +257 -0
- package/ts/capabilities/session/static-flight-golden.ts +209 -0
- package/ts/capabilities/session/wizard.ts +140 -0
- package/ts/capabilities/session-login.ts +63 -2
- package/ts/capabilities/session-search.ts +105 -3
- package/ts/capabilities/weather.ts +141 -52
- package/ts/package.json +3 -3
- package/ts/src/artifact-gate.ts +365 -0
- package/ts/src/benchmark-agent-conformance.ts +448 -0
- package/ts/src/benchmark-environment-bridge.ts +348 -0
- package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
- package/ts/src/benchmark-tool-isolation.ts +166 -0
- package/ts/src/bookable-facts.ts +557 -0
- package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
- package/ts/src/booking-surface/canonical-schema.js +113 -0
- package/ts/src/booking-surface/contracts-v2.ts +118 -0
- package/ts/src/booking-surface/contracts.ts +380 -0
- package/ts/src/booking-surface/dsh-planner.ts +452 -0
- package/ts/src/booking-surface/dsh-plugin.js +93 -0
- package/ts/src/booking-surface/error-codes.ts +101 -0
- package/ts/src/booking-surface/index.ts +12 -0
- package/ts/src/booking-surface/profile.ts +42 -0
- package/ts/src/booking-surface/runtime-v2.ts +1466 -0
- package/ts/src/booking-surface/runtime.ts +483 -0
- package/ts/src/booking-surface/server-v2.ts +247 -0
- package/ts/src/booking-surface/server.ts +324 -0
- package/ts/src/booking-surface/startup.ts +196 -0
- package/ts/src/booking-surface/validation-v2.ts +205 -0
- package/ts/src/booking-surface/validation.ts +453 -0
- package/ts/src/index.ts +161 -14
- package/ts/src/state-ledger.ts +1 -0
- package/ts/src/tool-budget.ts +165 -0
package/ts/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - gotry_wish_pool_add 「下一次出发」清单(憧憬不被拒绝)
|
|
8
8
|
*
|
|
9
9
|
* 插件形态遵循 dsh 约定(name/inject/Config/apply + ctx.tools.register(defineTool(...))),
|
|
10
|
-
*
|
|
10
|
+
* 对齐 @deepseek-ai/dsh-tools@0.1.2-alpha.3 的契约:
|
|
11
11
|
* render 位于 output 对象内,参数属性是 ValueSchemaSpec(支持 type:'json')。
|
|
12
12
|
*
|
|
13
13
|
* @module @gotry/plugin
|
|
@@ -35,11 +35,38 @@ import { anythingSearch } from '../capabilities/anything.ts'
|
|
|
35
35
|
import { readUrl, reach, reachStatus } from '../capabilities/agent-reach.ts'
|
|
36
36
|
import { videoSubtitle, githubSearch } from '../capabilities/agent-reach-deep.ts'
|
|
37
37
|
import { sessionLogin } from '../capabilities/session-login.ts'
|
|
38
|
+
import { EXTENSION_STORE_URL } from '../capabilities/session/extension-bridge.ts'
|
|
38
39
|
import { createConsentGate, approvalFromContext } from '../capabilities/session-consent.ts'
|
|
40
|
+
import { installModelOverride } from '../capabilities/model-override.ts'
|
|
39
41
|
import { listArtifacts, readArtifact } from '../capabilities/artifacts.ts'
|
|
40
42
|
import { interpretEffect, declinedObservation } from '../capabilities/effect.ts'
|
|
43
|
+
import { appendFacts, loadFactRegistry } from '../capabilities/fact-log.ts'
|
|
44
|
+
import { factsFromFlyai, factsFromSession } from './bookable-facts.ts'
|
|
45
|
+
import { gateArtifact, type AirlineAirportMap } from './artifact-gate.ts'
|
|
46
|
+
import { installToolBudget } from './tool-budget.ts'
|
|
47
|
+
import { registerBenchmarkEnvironmentBridge, type BenchmarkSubprocessService } from './benchmark-environment-bridge.ts'
|
|
48
|
+
import { installBenchmarkToolIsolation } from './benchmark-tool-isolation.ts'
|
|
49
|
+
import { installBenchmarkAgentConformance } from './benchmark-agent-conformance.ts'
|
|
50
|
+
|
|
51
|
+
/** 航司→机场映射表(issue #46 冲突检测面;data/airline-airports.json,as_of 快照) */
|
|
52
|
+
let airlineAirportMapCache: AirlineAirportMap | null = null
|
|
53
|
+
async function loadAirlineAirportMap(): Promise<AirlineAirportMap | null> {
|
|
54
|
+
if (airlineAirportMapCache) return airlineAirportMapCache
|
|
55
|
+
try {
|
|
56
|
+
const { readFile } = await import('node:fs/promises')
|
|
57
|
+
airlineAirportMapCache = JSON.parse(
|
|
58
|
+
await readFile(join(import.meta.dirname, '..', '..', 'data', 'airline-airports.json'), 'utf-8'),
|
|
59
|
+
) as AirlineAirportMap
|
|
60
|
+
return airlineAirportMapCache
|
|
61
|
+
} catch {
|
|
62
|
+
return null // 映射缺失不阻塞检索主路径;闸工具侧另行显式报错
|
|
63
|
+
}
|
|
64
|
+
}
|
|
41
65
|
|
|
42
66
|
export const name = 'gotry-tools'
|
|
67
|
+
// The bridge obtains subprocess through an optional Cordis lookup at apply
|
|
68
|
+
// time. Declaring it as a required injection would make the whole plugin
|
|
69
|
+
// depend on a service that is only needed for explicit benchmark opt-in.
|
|
43
70
|
export const inject = ['tools', 'systemPrompt']
|
|
44
71
|
|
|
45
72
|
export interface Config {
|
|
@@ -51,6 +78,8 @@ export interface Config {
|
|
|
51
78
|
hbcliBin: string
|
|
52
79
|
/** 账号会话检索总闸(RFC 支柱④「用户明示授权+随时可关」):ask=gotry_session_search 每会话每站点首次调用弹审批卡、会话内记住(默认);allow=用户已在配置明示预授权(直接放行);off=总闸关闭,直接拒绝 */
|
|
53
80
|
sessionAccess: string
|
|
81
|
+
/** Owner-local benchmark environment bridge config; empty disables the bridge. */
|
|
82
|
+
benchmarkEnvironmentConfigPath?: string
|
|
54
83
|
}
|
|
55
84
|
|
|
56
85
|
export const Config: z<Config> = z.object({
|
|
@@ -58,6 +87,7 @@ export const Config: z<Config> = z.object({
|
|
|
58
87
|
timeoutMs: z.number().default(30_000),
|
|
59
88
|
hbcliBin: z.string().default('hbcli'),
|
|
60
89
|
sessionAccess: z.string().default('ask'),
|
|
90
|
+
benchmarkEnvironmentConfigPath: z.string().default(''),
|
|
61
91
|
})
|
|
62
92
|
|
|
63
93
|
interface FeasibilityResult {
|
|
@@ -134,6 +164,29 @@ type Json = string | number | boolean | null | Json[] | { [k: string]: Json }
|
|
|
134
164
|
type JsonObject = { [k: string]: Json }
|
|
135
165
|
|
|
136
166
|
export function apply(ctx: Context, config: Config): void {
|
|
167
|
+
installToolBudget(ctx)
|
|
168
|
+
|
|
169
|
+
const rawBenchmarkEnvironmentConfigPath = config.benchmarkEnvironmentConfigPath ?? ''
|
|
170
|
+
if (rawBenchmarkEnvironmentConfigPath.trim()) {
|
|
171
|
+
// Benchmark mode is a deliberately minimal kernel: only the model
|
|
172
|
+
// override and the environment bridge are installed. In particular,
|
|
173
|
+
// product prompt variables, process/consent guards, and guarded product
|
|
174
|
+
// tools must not be observable on this path.
|
|
175
|
+
installModelOverride(ctx as unknown as Parameters<typeof installModelOverride>[0])
|
|
176
|
+
const getService = (ctx as unknown as { get?: (name: string, fallback?: unknown) => unknown }).get
|
|
177
|
+
const directSubprocess = (typeof getService === 'function'
|
|
178
|
+
? getService.call(ctx, 'subprocess')
|
|
179
|
+
: undefined) as BenchmarkSubprocessService | undefined
|
|
180
|
+
const projection = registerBenchmarkEnvironmentBridge(
|
|
181
|
+
rawBenchmarkEnvironmentConfigPath,
|
|
182
|
+
tool => ctx.tools.register(tool),
|
|
183
|
+
directSubprocess,
|
|
184
|
+
)
|
|
185
|
+
installBenchmarkToolIsolation(ctx)
|
|
186
|
+
installBenchmarkAgentConformance(ctx, projection)
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
137
190
|
// 时间感知:注册动态变量,persona 里用 {{current_date}} 引用。
|
|
138
191
|
// 每次 assemble 时取系统时钟——LLM 始终知道「今天是几号」。
|
|
139
192
|
const sp = (ctx as unknown as Record<string, unknown>)['systemPrompt'] as {
|
|
@@ -175,6 +228,10 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
175
228
|
}))
|
|
176
229
|
}
|
|
177
230
|
|
|
231
|
+
// LLM_MODEL → dsh 会话面模型覆盖(issue #77;机制与分层见
|
|
232
|
+
// capabilities/model-override.ts 头注)。GOTRY_LLM_MODEL 未设时零行为变化。
|
|
233
|
+
installModelOverride(ctx as unknown as Parameters<typeof installModelOverride>[0])
|
|
234
|
+
|
|
178
235
|
// D-NEW 收尾:全部工具 execute 统一异常隔离——单个工具抛错/拒绝不再沿 cordis
|
|
179
236
|
// 传到 dsh 主循环,降级为结构化错误返回给 LLM + incident 落盘(incident-log.ts)。
|
|
180
237
|
const registerGuarded = (tool: ReturnType<typeof defineTool>): void => {
|
|
@@ -737,6 +794,10 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
737
794
|
const itp = await interpretEffect({ effect: 'FLYAI_SEARCH', params: { kind, origin: q.from, destination: q.to, depDate: q.date } })
|
|
738
795
|
if (!itp.result) return declinedObservation('FLYAI_SEARCH', itp.trace)
|
|
739
796
|
const r = itp.result
|
|
797
|
+
// issue #46 事实落账(ADR-19):exact-date 检索结论(hit 正事实 / miss 负事实)追加进
|
|
798
|
+
// bookable-facts 侧车——产物事实闸(gotry_fact_gate)的唯一事实源;落盘失败不阻塞检索
|
|
799
|
+
const avMap = await loadAirlineAirportMap()
|
|
800
|
+
await appendFacts(config.stateRoot ?? '.', factsFromFlyai({ kind, origin: q.from, destination: q.to, date: q.date }, r, new Date().toISOString(), avMap?.city_alias))
|
|
740
801
|
const top = (r.options ?? []).slice(0, 8).map(o => `${o.no} ${o.name} ${o.depDateTime.slice(11, 16)}→${o.arrDateTime.slice(11, 16)} ¥${o.price}`)
|
|
741
802
|
// issue #24:miss(上游正常返回 0 条)与 error(限流/网络)分开陈述,不再混写「无结果或失败」
|
|
742
803
|
// (过去日期已被上方预校验拦下,此处不会出现过期查询)
|
|
@@ -761,9 +822,10 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
761
822
|
name: 'gotry_session_login',
|
|
762
823
|
description:
|
|
763
824
|
'Productized login bootstrap for the account session channel (call this when gotry_session_search returns needs-login — the user never needs a terminal). ' + 'AUTO-DETECTION FIRST: it reads ticket-cookie NAMES before anything else — if the user already logged in (on the external site) it confirms instantly WITHOUT opening any page. '
|
|
764
|
-
+ 'OPENS the site login entry in the USER\'S OWN Chrome and waits for the user to finish logging in on the external site. '
|
|
825
|
+
+ 'OPENS the site login entry in the USER\'S OWN Chrome (foreground tab, left open for the user) and waits for the user to finish logging in on the external site. '
|
|
765
826
|
+ 'GoTry NEVER collects, stores, or transmits credentials: no passwords, no SMS codes, no cookie values — it only checks the boolean fact "already logged in" (reads cookie NAMES only, zero values). '
|
|
766
|
-
+ '
|
|
827
|
+
+ 'Transport: the GoTry Session Bridge browser extension (one-time install, ZERO Chrome system dialogs). '
|
|
828
|
+
+ `verdict logged-in (tickets detected) | pending (login tab opened, user not done yet — offer to re-check later) | needs-extension (one-time extension install: the verdict surfaces the Chrome Web Store installUrl as a clickable link for dsh UI to render — installation is a browser concern, not gotry's). `
|
|
767
829
|
+ 'Evidence [会话:<site>-login@ts].',
|
|
768
830
|
parameters: {
|
|
769
831
|
query: { type: 'json', required: true, description: '可空对象 {}: { waitSeconds?: number }(等待用户完成登录的上限秒数,默认 90,至多 300)' },
|
|
@@ -776,16 +838,29 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
776
838
|
? `${r.site} 登录完成确认(票据 cookie 名已检出,只读名字)。说明:登录是在携程官网、用你自己的浏览器完成的——gotry 全程未接触任何密码/验证码/cookie 值。现在可以继续会话检索了。${r.evidence}`
|
|
777
839
|
: r.verdict === 'pending'
|
|
778
840
|
? `登录入口已在你的 Chrome 打开;请在弹出的标签页里正常登录携程(登录由你在官网完成,不属于 gotry)。完成后说一声"继续",我再确认。gotry 只检查"是否已登录",永不收集你的账号信息。${r.evidence}`
|
|
779
|
-
: r.verdict === 'needs-
|
|
780
|
-
?
|
|
781
|
-
:
|
|
841
|
+
: r.verdict === 'needs-extension'
|
|
842
|
+
? `需要一次性安装 GoTry Session Bridge 浏览器扩展:这是浏览器的事,gotry 不弹面板不开剪贴板——请直接在 Chrome 应用商店一键装(add-to-chrome 自动更新)。storeUrl=${EXTENSION_STORE_URL}. 装好即生效,装完后告诉我「重试」即可。${r.evidence}`
|
|
843
|
+
: r.verdict === 'needs-attach'
|
|
844
|
+
? `cdp 车道需要一次性开启你 Chrome 的远程调试开关:在你的 Chrome 地址栏打开 chrome://inspect/#remote-debugging 并打开开关,然后说一声"重试"(默认走扩展车道,无需此步)。${r.evidence}`
|
|
845
|
+
: `登录引导未完成:${r.error ?? '未知原因'} ${r.evidence}`
|
|
782
846
|
return JSON.parse(JSON.stringify({ ...r, summary })) as Record<string, never>
|
|
783
847
|
},
|
|
784
848
|
presentCall: _args => ({ card: 'generic', title: '登录携程(用你自己的浏览器,不在 gotry 输入)', kind: 'fetch', rawInput: {} }),
|
|
785
849
|
presentResult: (_args, value) => {
|
|
786
|
-
const r = value as { verdict?: string; tickets?: string[] }
|
|
787
|
-
const
|
|
788
|
-
|
|
850
|
+
const r = value as { verdict?: string; tickets?: string[]; installUrl?: string }
|
|
851
|
+
const needsExt = r.verdict === 'needs-extension'
|
|
852
|
+
const label = r.verdict === 'logged-in'
|
|
853
|
+
? `已登录(${(r.tickets ?? []).length} 票据)`
|
|
854
|
+
: r.verdict === 'pending'
|
|
855
|
+
? '等待你在携程页面完成登录'
|
|
856
|
+
: needsExt
|
|
857
|
+
? '🧩 需装 GoTry Session Bridge 扩展(浏览器商店一键装,自动更新)'
|
|
858
|
+
: r.verdict ?? '降级'
|
|
859
|
+
const content: Array<{ type: 'text'; text: string }> = [{ type: 'text', text: String((value as { summary?: string }).summary ?? '') }]
|
|
860
|
+
// needs-extension:把商店 URL 渲成可点链接(浏览器自己当安装器,gotry 不插手)
|
|
861
|
+
// dsh presentResult 若支持 actions 字段,URL 可被原生渲染;否则 content 里也保留文本兜底
|
|
862
|
+
if (needsExt && r.installUrl) content.push({ type: 'text', text: `安装链接:${r.installUrl}` })
|
|
863
|
+
return { card: 'generic', title: `账号登录:${label}`, content }
|
|
789
864
|
},
|
|
790
865
|
}))
|
|
791
866
|
|
|
@@ -794,9 +869,10 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
794
869
|
description:
|
|
795
870
|
'Search on the USER\'S OWN logged-in browser session (Ctrip flights today; the account channel, not an anonymous instance). '
|
|
796
871
|
+ 'Consent gate: the FIRST call in a session asks the user via the runtime approval card; once granted it holds for the session, a refusal revokes it for the session (no repeat prompting). '
|
|
797
|
-
+ '
|
|
872
|
+
+ 'Transport: GoTry Session Bridge browser extension (one-time install) — the agent side never talks to Chrome debugging, ZERO system dialogs; read-only by construction (the extension never issues requests; it only passively forwards the site\'s own search responses; agent NEVER touches credentials/captcha; on captcha it stops and returns challenged). '
|
|
798
873
|
+ 'Currently ctrip-flight: sniffs the site search API for structured options. Evidence [会话:ctrip-flight@ts]. '
|
|
799
|
-
+ 'verdict needs-login = call gotry_session_login (
|
|
874
|
+
+ 'verdict needs-login = call gotry_session_login (opens the Ctrip login entry in the user\'s own foreground tab — no terminal, no credentials through GoTry); '
|
|
875
|
+
+ `needs-extension = one-time browser-extension install (Chrome Web Store one-click, installUrl is also surfaced as a clickable link in the verdict field for dsh UI to render) — the DEFAULT transport; cdp (chrome://inspect remote debugging) is a diagnostic fallback only via GOTRY_SESSION_TRANSPORT=cdp. `
|
|
800
876
|
+ 'Rate-limited (≥30s between same-site calls; a challenged/timeout verdict means STOP — never retry, fall back to other tools).',
|
|
801
877
|
parameters: {
|
|
802
878
|
query: { type: 'json', required: true, description: '{ from: "上海", to: "丽江", date: "2026-10-01" }' },
|
|
@@ -819,6 +895,9 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
819
895
|
})
|
|
820
896
|
if (!itp.result) return declinedObservation('SESSION_FLIGHT_SEARCH', itp.trace)
|
|
821
897
|
const r = itp.result
|
|
898
|
+
// issue #46 事实落账(ADR-19):会话面 exact-date 结论同样进事实注册表(独立 source 并列标注)
|
|
899
|
+
const avMapS = await loadAirlineAirportMap()
|
|
900
|
+
await appendFacts(config.stateRoot ?? '.', factsFromSession({ origin: q.from, destination: q.to, date: q.date }, r, new Date().toISOString(), avMapS?.city_alias))
|
|
822
901
|
const top = (r.options ?? []).slice(0, 8).map(o => `${o.flightNo} ${o.airline} ${o.depDateTime.slice(11, 16)}→${o.arrDateTime.slice(11, 16)} ¥${o.price}`)
|
|
823
902
|
const summary = r.verdict === 'hit'
|
|
824
903
|
? `${q.from}→${q.to} ${q.date} 会话检索(携程,用户本人登录态)前 ${top.length} 条:\n${top.join('\n')}\n${r.evidence}`
|
|
@@ -827,9 +906,18 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
827
906
|
},
|
|
828
907
|
presentCall: args => ({ card: 'generic', title: `会话检索:${String((args.query as { from?: string })?.from ?? '')}`, kind: 'fetch', rawInput: args.query }),
|
|
829
908
|
presentResult: (_args, value) => {
|
|
830
|
-
const r = value as { verdict?: string; options?: unknown[] }
|
|
831
|
-
const
|
|
832
|
-
|
|
909
|
+
const r = value as { verdict?: string; options?: unknown[]; installUrl?: string }
|
|
910
|
+
const needsExt = r.verdict === 'needs-extension'
|
|
911
|
+
const label = r.verdict === 'hit'
|
|
912
|
+
? `会话 ${r.options!.length} 条`
|
|
913
|
+
: r.verdict === 'needs-login'
|
|
914
|
+
? '需登录'
|
|
915
|
+
: needsExt
|
|
916
|
+
? '🧩 需装 GoTry Session Bridge 扩展(浏览器商店一键装,自动更新)'
|
|
917
|
+
: r.verdict ?? '降级'
|
|
918
|
+
const content: Array<{ type: 'text'; text: string }> = [{ type: 'text', text: String((value as { summary?: string }).summary ?? '') }]
|
|
919
|
+
if (needsExt && r.installUrl) content.push({ type: 'text', text: `安装链接:${r.installUrl}` })
|
|
920
|
+
return { card: 'generic', title: `会话检索:${label}`, content }
|
|
833
921
|
},
|
|
834
922
|
}))
|
|
835
923
|
|
|
@@ -1181,4 +1269,63 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
1181
1269
|
}
|
|
1182
1270
|
},
|
|
1183
1271
|
}))
|
|
1272
|
+
|
|
1273
|
+
// ---- 产物事实闸(issue #46,ADR-19):含可下单事实的产物交付前必过 ----
|
|
1274
|
+
// 每个航班号/时刻/机场/价格/政策断言必须回溯到本会话 exact-date 工具结果
|
|
1275
|
+
// (bookable-facts 侧车,query_id 可重放);无法回溯即 blocked——不得宣称「已验证方案」。
|
|
1276
|
+
|
|
1277
|
+
registerGuarded(defineTool({
|
|
1278
|
+
name: 'gotry_fact_gate',
|
|
1279
|
+
description:
|
|
1280
|
+
'Factuality gate for itinerary artifacts (issue #46): call BEFORE delivering any artifact that contains bookable facts '
|
|
1281
|
+
+ '(flight numbers, times, airports, prices, visa/entry policies). Every bookable claim in the markdown is checked against '
|
|
1282
|
+
+ 'the session fact registry (exact-date tool results recorded by gotry_flyai_search / gotry_session_search — hit AND miss). '
|
|
1283
|
+
+ 'Blocked when: a claimed flight is absent from the exact-date source (never backfill from route pages/history/adjacent dates); '
|
|
1284
|
+
+ 'route+date never queried; times contradict the snapshot; carrier→airport mapping conflicts (FD=DMK vs VZ=BKK are NOT interchangeable); '
|
|
1285
|
+
+ '「联程」without protected_connection=true; policy claims without 「截至 YYYY-MM-DD」; unconditional ✓ on unverified claims. '
|
|
1286
|
+
+ 'Optional itinerary object enables machine invariants (hotel_nights + onboard_nights = total nights, O&D segments vs flight legs, '
|
|
1287
|
+
+ 'budget floor ≥ sum of item minimums). verdict=blocked ⇒ fix or downgrade wording — never present as a verified plan.',
|
|
1288
|
+
parameters: {
|
|
1289
|
+
query: {
|
|
1290
|
+
type: 'json',
|
|
1291
|
+
required: true,
|
|
1292
|
+
description: '{ markdown?: "<产物全文>", path?: "<产物路径(artifacts_list 返回的)>— 二选一", tripYear?: 2027, itinerary?: { trip_start, trip_end, stays: [{place,check_in,check_out}], onboard_nights, od_segments: [{from,to,date,mode,legs}], budget_items: [{label,min_cny,max_cny}], claimed_floor_cny? } }',
|
|
1293
|
+
},
|
|
1294
|
+
},
|
|
1295
|
+
output: {
|
|
1296
|
+
schema: { type: 'json' },
|
|
1297
|
+
render: (_args, value) => [{ type: 'text', text: String((value as { summary?: string }).summary ?? JSON.stringify(value).slice(0, 800)) }],
|
|
1298
|
+
},
|
|
1299
|
+
async execute(args: { query: unknown }, _exec: unknown) {
|
|
1300
|
+
const q = unwrapQuery<{ markdown?: string; path?: string; tripYear?: number; itinerary?: Record<string, unknown> }>(args, 'markdown')
|
|
1301
|
+
const avMap = await loadAirlineAirportMap()
|
|
1302
|
+
if (!avMap) {
|
|
1303
|
+
return JSON.parse(JSON.stringify({ ok: false, summary: '航司机场映射缺失(data/airline-airports.json)——事实闸不可用,fail closed:产物不得宣称已验证' })) as Record<string, never>
|
|
1304
|
+
}
|
|
1305
|
+
let markdown = q.markdown
|
|
1306
|
+
if (!markdown && q.path) {
|
|
1307
|
+
const r = await readArtifact({ stateRoot: config.stateRoot ?? '.', path: q.path })
|
|
1308
|
+
if (!r.ok) return JSON.parse(JSON.stringify({ ok: false, summary: `产物读取失败:${String((r as { error?: string }).error ?? '')}` })) as Record<string, never>
|
|
1309
|
+
markdown = r.content
|
|
1310
|
+
}
|
|
1311
|
+
if (!markdown) return JSON.parse(JSON.stringify({ ok: false, summary: '需要 markdown(产物全文)或 path(产物路径)' })) as Record<string, never>
|
|
1312
|
+
const itinerary = q.itinerary as import('./bookable-facts.ts').ItineraryFacts | undefined
|
|
1313
|
+
const registry = await loadFactRegistry(config.stateRoot ?? '.')
|
|
1314
|
+
const report = gateArtifact(markdown, registry, avMap, { trip_year: q.tripYear, itinerary })
|
|
1315
|
+
const lines = report.violations.slice(0, 20).map(v => ` L${v.line} [${v.kind}] ${v.detail}`)
|
|
1316
|
+
const summary = report.verdict === 'pass'
|
|
1317
|
+
? `事实闸 PASS:${report.traceable}/${report.claims_checked} 可下单 claim 全部回溯到 exact-date 工具结果(query_id 可重放)——可宣称「已验证方案」。`
|
|
1318
|
+
: `事实闸 BLOCKED(${report.violations.length} 违例,${report.traceable}/${report.claims_checked} claim 可回溯)——不得宣称「已验证方案」:\n${lines.join('\n')}\n修正路径:逐条改成 exact-date 源返回的事实,或降级为「未确认/当前不可售,到 D-xx 复核」;exact-date miss 的 route+date 不得用历史班期/相邻日期/航线页填充。`
|
|
1319
|
+
return JSON.parse(JSON.stringify({ ok: true, ...report, summary })) as Record<string, never>
|
|
1320
|
+
},
|
|
1321
|
+
presentCall: args => ({ card: 'generic', title: '产物事实闸', kind: 'execute', rawInput: args.query }),
|
|
1322
|
+
presentResult: (_args, value) => {
|
|
1323
|
+
const r = value as { verdict?: string; violations?: unknown[]; summary?: string }
|
|
1324
|
+
return {
|
|
1325
|
+
card: 'generic',
|
|
1326
|
+
title: `事实闸:${r.verdict === 'pass' ? '✅ pass' : `⛔ blocked(${r.violations?.length ?? '?'})`}`,
|
|
1327
|
+
content: [{ type: 'text', text: String(r.summary ?? '') }],
|
|
1328
|
+
}
|
|
1329
|
+
},
|
|
1330
|
+
}))
|
|
1184
1331
|
}
|
package/ts/src/state-ledger.ts
CHANGED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/** Per-agent, per-turn guard for model-driven tool loops. */
|
|
2
|
+
|
|
3
|
+
import { randomUUID } from 'node:crypto'
|
|
4
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
5
|
+
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
6
|
+
import type { ToolExecutionResult, ToolExecutionToken } from '@deepseek-ai/dsh-tools'
|
|
7
|
+
|
|
8
|
+
export const TOOL_BUDGET_SOFT_CALL = 16
|
|
9
|
+
export const TOOL_BUDGET_HARD_CALL = 18
|
|
10
|
+
export const TOOL_BUDGET_EXHAUSTED = 'TOOL_BUDGET_EXHAUSTED'
|
|
11
|
+
|
|
12
|
+
export type ToolBudgetDecision =
|
|
13
|
+
| { kind: 'allow'; softSignal: boolean; exhaustsBudget: boolean }
|
|
14
|
+
| { kind: 'deny'; reason: string }
|
|
15
|
+
|
|
16
|
+
export function toolBudgetDecision(callNumber: number): ToolBudgetDecision {
|
|
17
|
+
if (callNumber > TOOL_BUDGET_HARD_CALL) {
|
|
18
|
+
return {
|
|
19
|
+
kind: 'deny',
|
|
20
|
+
reason: `${TOOL_BUDGET_EXHAUSTED}: maximum ${TOOL_BUDGET_HARD_CALL} real tool dispatches per agent turn reached; provide a final answer using existing results`,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
kind: 'allow',
|
|
25
|
+
softSignal: callNumber === TOOL_BUDGET_SOFT_CALL,
|
|
26
|
+
exhaustsBudget: callNumber === TOOL_BUDGET_HARD_CALL,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createToolBudgetState(): {
|
|
31
|
+
nextCall(): ToolBudgetDecision & { callNumber: number }
|
|
32
|
+
} {
|
|
33
|
+
let calls = 0
|
|
34
|
+
return {
|
|
35
|
+
nextCall() {
|
|
36
|
+
calls += 1
|
|
37
|
+
return { ...toolBudgetDecision(calls), callNumber: calls }
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function convergenceContext(code: 'TOOL_BUDGET_SOFT' | typeof TOOL_BUDGET_EXHAUSTED, text: string) {
|
|
43
|
+
return {
|
|
44
|
+
id: randomUUID(),
|
|
45
|
+
role: 'user' as const,
|
|
46
|
+
content: [{ type: 'text' as const, text: `${code}: ${text}` }],
|
|
47
|
+
source: { kind: 'plugin' as const, plugin: 'gotry-tool-budget' },
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function exhaustionResult(reason: string): ToolExecutionResult {
|
|
52
|
+
return {
|
|
53
|
+
isError: true,
|
|
54
|
+
error: {
|
|
55
|
+
message: reason,
|
|
56
|
+
info: { name: 'ToolBudgetError', code: TOOL_BUDGET_EXHAUSTED },
|
|
57
|
+
},
|
|
58
|
+
content: [{ type: 'text', text: reason }],
|
|
59
|
+
additionalContexts: [convergenceContext(
|
|
60
|
+
TOOL_BUDGET_EXHAUSTED,
|
|
61
|
+
'No more tool dispatches are permitted in this turn. Produce the final answer from existing results.',
|
|
62
|
+
) as never],
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type RestrictableTools = {
|
|
67
|
+
schemas?: () => Array<{ name: string }>
|
|
68
|
+
restrict?: (filter: { deny: string[] }) => () => void
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Install the runtime boundary, kept separate from the GoTry tool catalog so
|
|
73
|
+
* the actual Cordis waterfalls can be exercised without booting every tool.
|
|
74
|
+
*/
|
|
75
|
+
export function installToolBudget(ctx: Context): void {
|
|
76
|
+
// Some offline contract tests intentionally provide a registration-only
|
|
77
|
+
// Context slice. They do not drive an agent loop, so there is no budget hook
|
|
78
|
+
// to install and no reason to require an event bus.
|
|
79
|
+
if (typeof (ctx as unknown as { on?: unknown }).on !== 'function') return
|
|
80
|
+
|
|
81
|
+
const budgetByAgent = new Map<string, ReturnType<typeof createToolBudgetState>>()
|
|
82
|
+
const softExecutions = new Map<ToolExecutionToken, string>()
|
|
83
|
+
const pendingFinalOnly = new Map<string, Agent>()
|
|
84
|
+
const finalOnlyDisposers = new Map<string, () => void>()
|
|
85
|
+
|
|
86
|
+
const clearAgent = (agentId: string) => {
|
|
87
|
+
budgetByAgent.delete(agentId)
|
|
88
|
+
for (const [token, owner] of softExecutions) {
|
|
89
|
+
if (owner === agentId) softExecutions.delete(token)
|
|
90
|
+
}
|
|
91
|
+
pendingFinalOnly.delete(agentId)
|
|
92
|
+
finalOnlyDisposers.get(agentId)?.()
|
|
93
|
+
finalOnlyDisposers.delete(agentId)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const enterFinalOnly = (agent: Agent) => {
|
|
97
|
+
const agentId = String(agent.id)
|
|
98
|
+
if (finalOnlyDisposers.has(agentId)) return
|
|
99
|
+
const globalTools = ctx.tools as unknown as RestrictableTools
|
|
100
|
+
const scopedTools = agent.ctx?.tools as unknown as RestrictableTools | undefined
|
|
101
|
+
if (typeof globalTools.schemas !== 'function' || typeof scopedTools?.restrict !== 'function') return
|
|
102
|
+
|
|
103
|
+
// GoTry's model-facing tools are inherited globals. Removing their schemas
|
|
104
|
+
// makes the next native-mode request text-only; the execution wrapper below
|
|
105
|
+
// still denies any already-prepared same-step call beyond the hard limit.
|
|
106
|
+
const inheritedNames = globalTools.schemas()
|
|
107
|
+
.map(schema => schema.name)
|
|
108
|
+
.filter(name => name !== 'run_code')
|
|
109
|
+
if (inheritedNames.length === 0) return
|
|
110
|
+
finalOnlyDisposers.set(agentId, scopedTools.restrict({ deny: inheritedNames }))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
ctx.on('session/event', (subject, event) => {
|
|
114
|
+
const agentId = String(subject.id)
|
|
115
|
+
if (event.type === 'turn/start' || event.type === 'turn/end') {
|
|
116
|
+
clearAgent(agentId)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
// The live registry re-checks a tool name before starting every call in
|
|
120
|
+
// one assistant response. Restricting immediately on call 18 would turn an
|
|
121
|
+
// already-prepared call 19 into an opaque "unknown tool" before the
|
|
122
|
+
// execute waterfall can return TOOL_BUDGET_EXHAUSTED. step/end is the
|
|
123
|
+
// exact boundary after that batch settles and before the next prompt is
|
|
124
|
+
// assembled, so current-step refusals stay structured while the next
|
|
125
|
+
// native request is text-only.
|
|
126
|
+
if (event.type === 'step/end') {
|
|
127
|
+
const agent = pendingFinalOnly.get(agentId)
|
|
128
|
+
if (agent) {
|
|
129
|
+
pendingFinalOnly.delete(agentId)
|
|
130
|
+
enterFinalOnly(agent)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
ctx.on('session/disposed', subject => clearAgent(String(subject.id)))
|
|
135
|
+
|
|
136
|
+
ctx.on('tools/execute', (exec, next) => {
|
|
137
|
+
// Programmatic/direct calls are not part of a model planner turn.
|
|
138
|
+
if (!exec.agent) return next()
|
|
139
|
+
const agentId = String(exec.agent.id)
|
|
140
|
+
const state = budgetByAgent.get(agentId) ?? createToolBudgetState()
|
|
141
|
+
budgetByAgent.set(agentId, state)
|
|
142
|
+
const decision = state.nextCall()
|
|
143
|
+
if (decision.kind === 'deny') {
|
|
144
|
+
pendingFinalOnly.set(agentId, exec.agent)
|
|
145
|
+
return Promise.resolve(exhaustionResult(decision.reason))
|
|
146
|
+
}
|
|
147
|
+
if (decision.softSignal) softExecutions.set(exec.token, agentId)
|
|
148
|
+
if (decision.exhaustsBudget) pendingFinalOnly.set(agentId, exec.agent)
|
|
149
|
+
return next()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
ctx.on('tools/post-execute', (exec, _result, next) => {
|
|
153
|
+
if (!softExecutions.delete(exec.token)) return next()
|
|
154
|
+
return next().then(decision => ({
|
|
155
|
+
...decision,
|
|
156
|
+
additionalContexts: [
|
|
157
|
+
...(decision.additionalContexts ?? []),
|
|
158
|
+
convergenceContext(
|
|
159
|
+
'TOOL_BUDGET_SOFT',
|
|
160
|
+
'Tool call 16 reached. Converge now and produce the final answer from gathered evidence.',
|
|
161
|
+
) as never,
|
|
162
|
+
],
|
|
163
|
+
}))
|
|
164
|
+
})
|
|
165
|
+
}
|