@danceiny/gotry 0.0.1-rc.18 → 0.0.1-rc.19
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 +42 -16
- package/README.zh-CN.md +37 -15
- package/bin/gotry-bootstrap.js +236 -3
- package/bin/gotry-inner.js +66 -20
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +7 -8
- package/cordis.gotry-patch.yml +15 -5
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach.js +4 -4
- package/dist/capabilities/channel-health.js +103 -0
- package/dist/capabilities/channel-registry.js +213 -0
- package/dist/capabilities/doctor.js +285 -0
- package/dist/capabilities/effect.js +178 -5
- package/dist/capabilities/flyai.js +13 -0
- package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
- package/dist/capabilities/session/adapters/rail-12306.js +285 -0
- package/dist/capabilities/session/extension-bridge.js +14 -1
- package/dist/capabilities/session/extension-channel.js +4 -2
- package/dist/capabilities/session/extension-distribution.js +1 -1
- package/dist/capabilities/session-search.js +311 -0
- package/dist/capabilities/visa-policy.js +126 -0
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +304 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
- package/dist/scripts/benchmark-environment-bridge-e2e.js +5 -10
- package/dist/scripts/benchmark-environment-bridge-tests.js +148 -142
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
- package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
- package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
- package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
- package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
- package/dist/scripts/bootstrap-tests.js +87 -2
- package/dist/scripts/build-metrics-report.js +339 -0
- package/dist/scripts/channel-probe-tests.js +148 -0
- package/dist/scripts/channel-probe.js +252 -0
- package/dist/scripts/channel-registry-tests.js +262 -0
- package/dist/scripts/doctor-tests.js +121 -0
- package/dist/scripts/effect-tests.js +274 -1
- package/dist/scripts/extension-tests.js +129 -11
- package/dist/scripts/fact-gate-tests.js +91 -2
- package/dist/scripts/flyai-tests.js +17 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -3
- package/dist/scripts/metrics-report-tests.js +288 -0
- package/dist/scripts/persona-surface-guard-tests.js +19 -0
- package/dist/scripts/session-tests.js +401 -1
- package/dist/scripts/smoke.js +100 -83
- package/dist/scripts/turn-handoff-collect-tests.js +216 -0
- package/dist/scripts/turn-handoff-collect.js +189 -0
- package/dist/scripts/turn-policy-tests.js +56 -0
- package/dist/scripts/typed-contract-canary.js +261 -0
- package/dist/scripts/visa-policy-tests.js +75 -0
- package/dist/scripts/wish-channel-gate-tests.js +111 -0
- package/dist/src/artifact-gate.js +91 -3
- package/dist/src/benchmark-agent-conformance.js +7 -4
- package/dist/src/benchmark-environment-bridge.js +68 -7
- package/dist/src/bookable-facts.js +62 -3
- package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
- package/dist/src/booking-surface/canonical-schema.js +8 -22
- package/dist/src/booking-surface/contracts.js +70 -19
- package/dist/src/booking-surface/dsh-planner.js +310 -177
- package/dist/src/booking-surface/dsh-plugin.js +3 -3
- package/dist/src/booking-surface/index.js +1 -5
- package/dist/src/booking-surface/profile.js +1 -1
- package/dist/src/booking-surface/runtime.js +1682 -247
- package/dist/src/booking-surface/server.js +371 -185
- package/dist/src/booking-surface/startup.js +17 -27
- package/dist/src/booking-surface/validation.js +270 -760
- package/dist/src/index.js +735 -149
- package/dist/src/turn-deadline.js +292 -0
- package/dist/src/turn-policy.js +146 -0
- package/dist/src/wish-pool.js +5 -0
- package/extension/background.js +23 -4
- package/extension/content-main.js +47 -9
- package/extension/manifest.json +27 -9
- package/package.json +6 -26
- package/schemas/booking.surface.schema.json +2593 -0
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/flyai.ts +21 -3
- package/ts/capabilities/session/action-cache.ts +1 -1
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
- package/ts/capabilities/session/extension-bridge.ts +32 -2
- package/ts/capabilities/session/extension-channel.ts +9 -8
- package/ts/capabilities/session/extension-distribution.ts +3 -1
- package/ts/capabilities/session/health-watch.ts +1 -1
- package/ts/capabilities/session/read-guard.ts +1 -1
- package/ts/capabilities/session/wizard.ts +1 -1
- package/ts/capabilities/session-search.ts +323 -1
- package/ts/package.json +1 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +82 -2
- package/ts/src/benchmark-agent-conformance.ts +14 -6
- package/ts/src/benchmark-environment-bridge.ts +29 -10
- package/ts/src/bookable-facts.ts +109 -5
- package/ts/src/booking-saga.ts +1 -1
- package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
- package/ts/src/booking-surface/canonical-schema.js +8 -22
- package/ts/src/booking-surface/contracts.ts +278 -239
- package/ts/src/booking-surface/dsh-planner.ts +333 -194
- package/ts/src/booking-surface/dsh-plugin.js +3 -3
- package/ts/src/booking-surface/index.ts +1 -5
- package/ts/src/booking-surface/profile.ts +11 -11
- package/ts/src/booking-surface/runtime.ts +1351 -351
- package/ts/src/booking-surface/server.ts +282 -175
- package/ts/src/booking-surface/startup.ts +36 -48
- package/ts/src/booking-surface/validation.ts +194 -442
- package/ts/src/contracts.ts +1 -1
- package/ts/src/index.ts +489 -171
- package/ts/src/loop.ts +1 -1
- package/ts/src/state-ledger.ts +1 -1
- package/ts/src/turn-deadline.ts +361 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/wish-pool.ts +17 -4
- package/dist/scripts/agent-planning-budget-e2e.js +0 -227
- package/dist/scripts/agent-planning-budget-tests.js +0 -173
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
- package/dist/src/booking-surface/contracts-v2.js +0 -89
- package/dist/src/booking-surface/runtime-v2.js +0 -1771
- package/dist/src/booking-surface/server-v2.js +0 -334
- package/dist/src/booking-surface/validation-v2.js +0 -319
- package/dist/src/tool-budget.js +0 -136
- package/schemas/booking.surface.v1.schema.json +0 -927
- package/schemas/booking.surface.v2.schema.json +0 -61
- package/ts/src/booking-surface/contracts-v2.ts +0 -118
- package/ts/src/booking-surface/runtime-v2.ts +0 -1466
- package/ts/src/booking-surface/server-v2.ts +0 -247
- package/ts/src/booking-surface/validation-v2.ts +0 -205
- package/ts/src/tool-budget.ts +0 -165
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { classifyTurn, TURN_HANDOFF_ETA_LABEL, turnPolicyFor } from './turn-policy.js';
|
|
5
|
+
export const TURN_DEADLINE_SOFT = 'TURN_DEADLINE_SOFT';
|
|
6
|
+
export const TURN_DEADLINE_EXHAUSTED = 'TURN_DEADLINE_EXHAUSTED';
|
|
7
|
+
export const TURN_DEADLINE_HANDOFF = 'TURN_DEADLINE_HANDOFF';
|
|
8
|
+
export const TURN_HANDOFF_SCHEMA = 'gotry_turn_handoff.v1';
|
|
9
|
+
export async function writeTurnHandoffTicket(stateRoot, userMessage) {
|
|
10
|
+
const ticket = {
|
|
11
|
+
schema: TURN_HANDOFF_SCHEMA,
|
|
12
|
+
id: `th-${Date.now().toString(36)}-${randomUUID().slice(0, 6)}`,
|
|
13
|
+
objective: userMessage.length > 120 ? `${userMessage.slice(0, 120)}…` : userMessage,
|
|
14
|
+
userMessage,
|
|
15
|
+
requestedAt: new Date().toISOString(),
|
|
16
|
+
etaLabel: TURN_HANDOFF_ETA_LABEL,
|
|
17
|
+
status: 'open'
|
|
18
|
+
};
|
|
19
|
+
await writeTicketJson(stateRoot, ticket);
|
|
20
|
+
return ticket;
|
|
21
|
+
}
|
|
22
|
+
function handoffDir(stateRoot) {
|
|
23
|
+
const root = stateRoot === '.' ? process.cwd() : stateRoot;
|
|
24
|
+
return join(root, 'gotry-state', 'turn-handoffs');
|
|
25
|
+
}
|
|
26
|
+
async function writeTicketJson(stateRoot, ticket) {
|
|
27
|
+
const dir = handoffDir(stateRoot);
|
|
28
|
+
await mkdir(dir, {
|
|
29
|
+
recursive: true
|
|
30
|
+
});
|
|
31
|
+
const path = join(dir, `${ticket.id}.json`);
|
|
32
|
+
const tmp = `${path}.tmp`;
|
|
33
|
+
await writeFile(tmp, JSON.stringify(ticket, null, 2), 'utf-8');
|
|
34
|
+
await rename(tmp, path);
|
|
35
|
+
return path;
|
|
36
|
+
}
|
|
37
|
+
export async function settleTurnHandoffTicket(stateRoot, ticket, status, deliverable, error) {
|
|
38
|
+
const dir = handoffDir(stateRoot);
|
|
39
|
+
await mkdir(dir, {
|
|
40
|
+
recursive: true
|
|
41
|
+
});
|
|
42
|
+
const deliverablePath = join(dir, `${ticket.id}.deliverable.md`);
|
|
43
|
+
const deliverableTmp = `${deliverablePath}.tmp`;
|
|
44
|
+
await writeFile(deliverableTmp, deliverable, 'utf-8');
|
|
45
|
+
await rename(deliverableTmp, deliverablePath);
|
|
46
|
+
const settled = {
|
|
47
|
+
...ticket,
|
|
48
|
+
status,
|
|
49
|
+
settledAt: new Date().toISOString(),
|
|
50
|
+
deliverableFile: `${ticket.id}.deliverable.md`,
|
|
51
|
+
...error ? {
|
|
52
|
+
error
|
|
53
|
+
} : {}
|
|
54
|
+
};
|
|
55
|
+
const ticketPath = await writeTicketJson(stateRoot, settled);
|
|
56
|
+
return {
|
|
57
|
+
ticketPath,
|
|
58
|
+
deliverablePath
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export async function listTurnHandoffTickets(stateRoot) {
|
|
62
|
+
const dir = handoffDir(stateRoot);
|
|
63
|
+
let names;
|
|
64
|
+
try {
|
|
65
|
+
names = await readdir(dir);
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const views = [];
|
|
70
|
+
for (const name of names){
|
|
71
|
+
if (!name.endsWith('.json')) continue;
|
|
72
|
+
try {
|
|
73
|
+
const raw = JSON.parse(await readFile(join(dir, name), 'utf-8'));
|
|
74
|
+
if (raw?.schema !== TURN_HANDOFF_SCHEMA) continue;
|
|
75
|
+
const view = {
|
|
76
|
+
id: raw.id,
|
|
77
|
+
status: raw.status,
|
|
78
|
+
objective: raw.objective,
|
|
79
|
+
requestedAt: raw.requestedAt,
|
|
80
|
+
etaLabel: raw.etaLabel,
|
|
81
|
+
...raw.settledAt ? {
|
|
82
|
+
settledAt: raw.settledAt
|
|
83
|
+
} : {},
|
|
84
|
+
...raw.error ? {
|
|
85
|
+
error: raw.error
|
|
86
|
+
} : {}
|
|
87
|
+
};
|
|
88
|
+
if (raw.status !== 'open' && raw.deliverableFile) {
|
|
89
|
+
try {
|
|
90
|
+
const text = await readFile(join(dir, raw.deliverableFile), 'utf-8');
|
|
91
|
+
view.deliverableExcerpt = text.length > 600 ? `${text.slice(0, 600)}…` : text;
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
views.push(view);
|
|
95
|
+
} catch {}
|
|
96
|
+
}
|
|
97
|
+
views.sort((a, b)=>a.requestedAt < b.requestedAt ? 1 : -1);
|
|
98
|
+
return views;
|
|
99
|
+
}
|
|
100
|
+
function resolveEnvPin(name) {
|
|
101
|
+
const raw = process.env[name];
|
|
102
|
+
if (!raw) return null;
|
|
103
|
+
const parsed = Number.parseInt(raw, 10);
|
|
104
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
105
|
+
}
|
|
106
|
+
function convergenceContext(code, text) {
|
|
107
|
+
return {
|
|
108
|
+
id: randomUUID(),
|
|
109
|
+
role: 'user',
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: 'text',
|
|
113
|
+
text: `${code}: ${text}`
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
source: {
|
|
117
|
+
kind: 'plugin',
|
|
118
|
+
plugin: 'gotry-turn-deadline'
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function exhaustionResult(reason) {
|
|
123
|
+
return {
|
|
124
|
+
isError: true,
|
|
125
|
+
error: {
|
|
126
|
+
message: reason,
|
|
127
|
+
info: {
|
|
128
|
+
name: 'TurnDeadlineError',
|
|
129
|
+
code: TURN_DEADLINE_EXHAUSTED
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: 'text',
|
|
135
|
+
text: reason
|
|
136
|
+
}
|
|
137
|
+
],
|
|
138
|
+
additionalContexts: [
|
|
139
|
+
convergenceContext(TURN_DEADLINE_EXHAUSTED, 'No more tool dispatches are permitted in this turn. Produce the final answer from existing results.')
|
|
140
|
+
]
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function handoffResult(ticket) {
|
|
144
|
+
const instruction = `Turn handed off to background planning. Async ticket ${ticket.id} created (ETA ${ticket.etaLabel}). Stop dispatching tools and tell the user in one short message: the deep planning continues in the background, the expected time, and to check back later (mention ticket ${ticket.id}).`;
|
|
145
|
+
return {
|
|
146
|
+
isError: true,
|
|
147
|
+
error: {
|
|
148
|
+
message: `${TURN_DEADLINE_HANDOFF}: sync window exhausted for a deep-planning turn; ticket ${ticket.id}`,
|
|
149
|
+
info: {
|
|
150
|
+
name: 'TurnDeadlineHandoff',
|
|
151
|
+
code: TURN_DEADLINE_HANDOFF
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: 'text',
|
|
157
|
+
text: `${TURN_DEADLINE_HANDOFF}: ${instruction}`
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
additionalContexts: [
|
|
161
|
+
convergenceContext(TURN_DEADLINE_HANDOFF, instruction)
|
|
162
|
+
]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export function installTurnDeadline(ctx, options = {}) {
|
|
166
|
+
if (options.enabled === false) return;
|
|
167
|
+
if (typeof ctx.on !== 'function') return;
|
|
168
|
+
const now = options.now ?? (()=>Date.now());
|
|
169
|
+
const softPin = resolveEnvPin('GOTRY_TURN_DEADLINE_SOFT_MS');
|
|
170
|
+
const hardPin = resolveEnvPin('GOTRY_TURN_DEADLINE_HARD_MS');
|
|
171
|
+
const defaultPolicy = options.fixedPolicy ?? turnPolicyFor('sync-planning');
|
|
172
|
+
const routePolicy = (message)=>{
|
|
173
|
+
if (options.fixedPolicy) return {
|
|
174
|
+
...options.fixedPolicy
|
|
175
|
+
};
|
|
176
|
+
const policy = turnPolicyFor(classifyTurn(message));
|
|
177
|
+
return {
|
|
178
|
+
...policy,
|
|
179
|
+
softMs: softPin ?? policy.softMs,
|
|
180
|
+
hardMs: hardPin ?? policy.hardMs
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
const stateByAgent = new Map();
|
|
184
|
+
const softExecutions = new Map();
|
|
185
|
+
const finalOnlyDisposers = new Map();
|
|
186
|
+
const clearAgent = (agentId)=>{
|
|
187
|
+
stateByAgent.delete(agentId);
|
|
188
|
+
for (const [token, owner] of softExecutions){
|
|
189
|
+
if (owner.agentId === agentId) softExecutions.delete(token);
|
|
190
|
+
}
|
|
191
|
+
finalOnlyDisposers.get(agentId)?.();
|
|
192
|
+
finalOnlyDisposers.delete(agentId);
|
|
193
|
+
};
|
|
194
|
+
const enterFinalOnly = (agent)=>{
|
|
195
|
+
const agentId = String(agent.id);
|
|
196
|
+
if (finalOnlyDisposers.has(agentId)) return;
|
|
197
|
+
const globalTools = ctx.tools;
|
|
198
|
+
const scopedTools = agent.ctx?.tools;
|
|
199
|
+
if (typeof globalTools.schemas !== 'function' || typeof scopedTools?.restrict !== 'function') {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const inheritedNames = globalTools.schemas().map((schema)=>schema.name).filter((name)=>name !== 'run_code');
|
|
203
|
+
if (inheritedNames.length === 0) return;
|
|
204
|
+
finalOnlyDisposers.set(agentId, scopedTools.restrict({
|
|
205
|
+
deny: inheritedNames
|
|
206
|
+
}));
|
|
207
|
+
};
|
|
208
|
+
const keyOfAgent = (agent)=>{
|
|
209
|
+
const session = agent.session;
|
|
210
|
+
return String(session?.id ?? agent.id);
|
|
211
|
+
};
|
|
212
|
+
ctx.on('session/event', (subject, event)=>{
|
|
213
|
+
const agentId = String(subject.id);
|
|
214
|
+
if (event.type === 'turn/end') {
|
|
215
|
+
clearAgent(agentId);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (event.type === 'turn/start') {
|
|
219
|
+
finalOnlyDisposers.get(agentId)?.();
|
|
220
|
+
finalOnlyDisposers.delete(agentId);
|
|
221
|
+
if (!stateByAgent.has(agentId)) {
|
|
222
|
+
stateByAgent.set(agentId, {
|
|
223
|
+
startedAt: now(),
|
|
224
|
+
policy: {
|
|
225
|
+
...defaultPolicy,
|
|
226
|
+
softMs: softPin ?? defaultPolicy.softMs,
|
|
227
|
+
hardMs: hardPin ?? defaultPolicy.hardMs
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (event.type === 'user/message') {
|
|
234
|
+
const message = event.data;
|
|
235
|
+
if (message?.source?.kind !== 'user') return;
|
|
236
|
+
const text = (message.content ?? []).find((block)=>block.type === 'text')?.text ?? '';
|
|
237
|
+
if (!text.trim()) return;
|
|
238
|
+
stateByAgent.set(agentId, {
|
|
239
|
+
startedAt: now(),
|
|
240
|
+
policy: routePolicy(text),
|
|
241
|
+
userMessage: text
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
ctx.on('session/disposed', (subject)=>clearAgent(String(subject.id)));
|
|
246
|
+
ctx.on('tools/execute', (exec, next)=>{
|
|
247
|
+
if (!exec.agent) return next();
|
|
248
|
+
const agentId = keyOfAgent(exec.agent);
|
|
249
|
+
const state = stateByAgent.get(agentId) ?? {
|
|
250
|
+
startedAt: now(),
|
|
251
|
+
policy: {
|
|
252
|
+
...defaultPolicy,
|
|
253
|
+
softMs: softPin ?? defaultPolicy.softMs,
|
|
254
|
+
hardMs: hardPin ?? defaultPolicy.hardMs
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
stateByAgent.set(agentId, state);
|
|
258
|
+
const elapsed = now() - state.startedAt;
|
|
259
|
+
if (elapsed > state.policy.hardMs) {
|
|
260
|
+
enterFinalOnly(exec.agent);
|
|
261
|
+
if (state.policy.exit === 'handoff' && state.userMessage && options.stateRoot) {
|
|
262
|
+
return writeTurnHandoffTicket(options.stateRoot, state.userMessage).then((ticket)=>handoffResult(ticket)).catch(()=>exhaustionResult(`${TURN_DEADLINE_EXHAUSTED}: hard deadline ${state.policy.hardMs}ms reached (ticket persistence failed; degraded to converge)`));
|
|
263
|
+
}
|
|
264
|
+
return Promise.resolve(exhaustionResult(`${TURN_DEADLINE_EXHAUSTED}: turn exceeded hard deadline of ${state.policy.hardMs}ms (elapsed ${elapsed}ms); provide a final answer using existing results`));
|
|
265
|
+
}
|
|
266
|
+
if (elapsed > state.policy.softMs) {
|
|
267
|
+
softExecutions.set(exec.token, {
|
|
268
|
+
agentId,
|
|
269
|
+
exit: state.policy.exit
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return next();
|
|
273
|
+
});
|
|
274
|
+
ctx.on('tools/post-execute', (exec, _result, next)=>{
|
|
275
|
+
return next().then((decision)=>{
|
|
276
|
+
const owner = softExecutions.get(exec.token);
|
|
277
|
+
if (!owner) return decision;
|
|
278
|
+
softExecutions.delete(exec.token);
|
|
279
|
+
const text = owner.exit === 'handoff' ? 'Turn has crossed the soft deadline of a deep-planning turn. Wrap up context gathering now; the turn will hand off to background planning at the hard deadline.' : 'Turn has crossed the soft deadline. Stop dispatching new tools and produce the final answer from gathered evidence.';
|
|
280
|
+
return {
|
|
281
|
+
...decision,
|
|
282
|
+
additionalContexts: [
|
|
283
|
+
...decision.additionalContexts ?? [],
|
|
284
|
+
convergenceContext(TURN_DEADLINE_SOFT, text)
|
|
285
|
+
]
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
//# sourceURL=ts/src/turn-deadline.ts
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export const TURN_POLICIES = {
|
|
2
|
+
quick: {
|
|
3
|
+
softMs: 120_000,
|
|
4
|
+
hardMs: 180_000,
|
|
5
|
+
exit: 'converge'
|
|
6
|
+
},
|
|
7
|
+
'sync-planning': {
|
|
8
|
+
softMs: 300_000,
|
|
9
|
+
hardMs: 600_000,
|
|
10
|
+
exit: 'converge'
|
|
11
|
+
},
|
|
12
|
+
'deep-planning': {
|
|
13
|
+
softMs: 120_000,
|
|
14
|
+
hardMs: 240_000,
|
|
15
|
+
exit: 'handoff'
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
export const TURN_HANDOFF_ETA_LABEL = '约 1 小时';
|
|
19
|
+
export const TURN_CONSTRAINT_LEXICON = [
|
|
20
|
+
'婚礼',
|
|
21
|
+
'婚宴',
|
|
22
|
+
'回门',
|
|
23
|
+
'订婚',
|
|
24
|
+
'IRW',
|
|
25
|
+
'irw',
|
|
26
|
+
'请假',
|
|
27
|
+
'年假',
|
|
28
|
+
'调休',
|
|
29
|
+
'额度',
|
|
30
|
+
'签证',
|
|
31
|
+
'同行',
|
|
32
|
+
'伴侣',
|
|
33
|
+
'亲子',
|
|
34
|
+
'带娃',
|
|
35
|
+
'父母',
|
|
36
|
+
'团建',
|
|
37
|
+
'出差',
|
|
38
|
+
'红眼',
|
|
39
|
+
'中转',
|
|
40
|
+
'联程',
|
|
41
|
+
'多城'
|
|
42
|
+
];
|
|
43
|
+
const CN_DIGITS = {
|
|
44
|
+
一: 1,
|
|
45
|
+
二: 2,
|
|
46
|
+
两: 2,
|
|
47
|
+
三: 3,
|
|
48
|
+
四: 4,
|
|
49
|
+
五: 5,
|
|
50
|
+
六: 6,
|
|
51
|
+
七: 7,
|
|
52
|
+
八: 8,
|
|
53
|
+
九: 9,
|
|
54
|
+
十: 10
|
|
55
|
+
};
|
|
56
|
+
function dayCount(year, month, day) {
|
|
57
|
+
const y = year ?? 1970;
|
|
58
|
+
return Date.UTC(y, month - 1, day) / 86_400_000;
|
|
59
|
+
}
|
|
60
|
+
function spanWordDays(message) {
|
|
61
|
+
if (/十几天/.test(message)) return 12;
|
|
62
|
+
if (/半个月/.test(message)) return 15;
|
|
63
|
+
if (/一个月/.test(message)) return 30;
|
|
64
|
+
const week = message.match(/[一两]个?(?:星期|周)/);
|
|
65
|
+
if (week) return week[0].startsWith('两') ? 14 : 7;
|
|
66
|
+
const dayMatch = message.match(/([0-9一二两三四五六七八九十]+)\s*天/);
|
|
67
|
+
if (dayMatch) {
|
|
68
|
+
const raw = dayMatch[1];
|
|
69
|
+
if (/^[0-9]+$/.test(raw)) return Math.min(Number.parseInt(raw, 10), 60);
|
|
70
|
+
if (CN_DIGITS[raw] !== undefined) return Math.min(CN_DIGITS[raw], 60);
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
export function extractTurnSignals(message) {
|
|
75
|
+
const absoluteDates = [];
|
|
76
|
+
let rest = message.replace(/(?<!\d)(\d{4})[年\-.\/](\d{1,2})[月\-.\/](\d{1,2})日?(?!\d)/g, (_all, y, m, d)=>{
|
|
77
|
+
const month = Number.parseInt(m, 10);
|
|
78
|
+
const day = Number.parseInt(d, 10);
|
|
79
|
+
if (month >= 1 && month <= 12 && day >= 1 && day <= 31) {
|
|
80
|
+
absoluteDates.push({
|
|
81
|
+
year: Number.parseInt(y, 10),
|
|
82
|
+
month,
|
|
83
|
+
day
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return ' ';
|
|
87
|
+
});
|
|
88
|
+
rest = rest.replace(/(?<!\d)(\d{1,2})月(\d{1,2})日?(?!\d)/g, (_all, m, d)=>{
|
|
89
|
+
const month = Number.parseInt(m, 10);
|
|
90
|
+
const day = Number.parseInt(d, 10);
|
|
91
|
+
if (month >= 1 && month <= 12 && day >= 1 && day <= 31) {
|
|
92
|
+
absoluteDates.push({
|
|
93
|
+
year: null,
|
|
94
|
+
month,
|
|
95
|
+
day
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return ' ';
|
|
99
|
+
});
|
|
100
|
+
rest = rest.replace(/(?<!\d)(\d{1,2})[.\/](\d{1,2})(?!\d)/g, (_all, m, d)=>{
|
|
101
|
+
const month = Number.parseInt(m, 10);
|
|
102
|
+
const day = Number.parseInt(d, 10);
|
|
103
|
+
if (month >= 1 && month <= 12 && day >= 1 && day <= 31) {
|
|
104
|
+
absoluteDates.push({
|
|
105
|
+
year: null,
|
|
106
|
+
month,
|
|
107
|
+
day
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return ' ';
|
|
111
|
+
});
|
|
112
|
+
const constraintHits = TURN_CONSTRAINT_LEXICON.filter((word)=>message.includes(word));
|
|
113
|
+
let spanDays = null;
|
|
114
|
+
if (absoluteDates.length >= 2) {
|
|
115
|
+
const counts = absoluteDates.map((d)=>dayCount(d.year, d.month, d.day));
|
|
116
|
+
spanDays = Math.max(...counts) - Math.min(...counts);
|
|
117
|
+
} else {
|
|
118
|
+
spanDays = spanWordDays(message);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
messageLength: message.length,
|
|
122
|
+
absoluteDates,
|
|
123
|
+
spanDays,
|
|
124
|
+
constraintHits
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function classifyTurn(message) {
|
|
128
|
+
const s = extractTurnSignals(message);
|
|
129
|
+
if (s.spanDays !== null && s.spanDays >= 7) return 'deep-planning';
|
|
130
|
+
if (s.absoluteDates.length >= 2 && s.constraintHits.length >= 1) return 'deep-planning';
|
|
131
|
+
if (s.constraintHits.length >= 2 && (s.absoluteDates.length >= 1 || s.spanDays !== null)) {
|
|
132
|
+
return 'deep-planning';
|
|
133
|
+
}
|
|
134
|
+
if (s.absoluteDates.length === 0 && s.constraintHits.length === 0 && s.spanDays === null && s.messageLength <= 60) {
|
|
135
|
+
return 'quick';
|
|
136
|
+
}
|
|
137
|
+
return 'sync-planning';
|
|
138
|
+
}
|
|
139
|
+
export function turnPolicyFor(taskClass) {
|
|
140
|
+
return {
|
|
141
|
+
...TURN_POLICIES[taskClass]
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
//# sourceURL=ts/src/turn-policy.ts
|
package/dist/src/wish-pool.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export function scoreWishMatch(entry, ctx) {
|
|
2
2
|
const c = entry.conditions ?? {};
|
|
3
|
+
if (ctx.channelDown && Array.isArray(c.channels)) {
|
|
4
|
+
for (const ch of c.channels){
|
|
5
|
+
if (typeof ch === 'string' && ctx.channelDown.has(ch)) return null;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
3
8
|
const hits = [];
|
|
4
9
|
if (typeof ctx.days === 'number' && typeof c.days === 'number' && ctx.days >= c.days) hits.push(`days≥${c.days}`);
|
|
5
10
|
if (typeof ctx.budgetCny === 'number' && typeof c.budget_cny === 'number' && ctx.budgetCny >= c.budget_cny) hits.push(`budget≥${c.budget_cny}`);
|
package/extension/background.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GoTry Session Bridge — MV3 Service Worker(RFC user-session-data-rfc.md §2.2 通道 C,2026-08-29 定案为 PRIMARY 传输)。
|
|
2
|
+
* GoTry Session Bridge — MV3 Service Worker(RFC docs/rfc/user-session-data-rfc.md §2.2 通道 C,2026-08-29 定案为 PRIMARY 传输)。
|
|
3
3
|
*
|
|
4
4
|
* 职责(与 Node 侧 session/extension-bridge.ts 配对,零构建纯 JS):
|
|
5
5
|
* - 长轮询取活:POST /jobs(桥最多 hold 20s;每次响应/失败都重置 SW 30s 生命周期,任务秒级触达);
|
|
6
6
|
* 断线每 5s 重试,chrome.alarms(30s)兜底唤醒防 SW 悬挂。
|
|
7
7
|
* - 三种 job:
|
|
8
|
-
* search → 后台标签打开 job.url(
|
|
8
|
+
* search → 后台标签打开 job.url(per-site 白名单:flights/hotels.ctrip.com),等 content hook 嗅探回包,收尾关自己的标签;
|
|
9
9
|
* open-login → 置前台打开登录入口页(登录页纪律 #34),标签留给用户,绝不代关;
|
|
10
10
|
* cookie-names → chrome.cookies 只读票据 cookie **名字**(值即取即弃,永不离开扩展——红线)。
|
|
11
11
|
* - 物理只读(ReadGuard 扩展车道形态):本 SW 绝不向站点发任何请求——检索请求由站点自己的
|
|
@@ -23,6 +23,24 @@ const SITES = {
|
|
|
23
23
|
domain: 'ctrip.com',
|
|
24
24
|
ticketNames: ['cticket', 'uid', 'uname', 'passport'],
|
|
25
25
|
},
|
|
26
|
+
// 酒店(2026-09-03 实装):同一携程账号体系;检索走 hotels.ctrip.com 后台标签 + 被动嗅探
|
|
27
|
+
'ctrip-hotel': {
|
|
28
|
+
domain: 'ctrip.com',
|
|
29
|
+
ticketNames: ['cticket', 'uid', 'uname', 'passport'],
|
|
30
|
+
},
|
|
31
|
+
// 火车(2026-09-03 实装):12306 余票查询是公开面(登录只关系下单),无票据名可检;
|
|
32
|
+
// search job 照常经 per-site 白名单,扩展只读被动嗅探
|
|
33
|
+
'train-12306': {
|
|
34
|
+
domain: '12306.cn',
|
|
35
|
+
ticketNames: [],
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 检索 URL 白名单(per-site;search job 只允许开各自站点域,其余一律拒) */
|
|
40
|
+
const SITE_SEARCH_PREFIXES = {
|
|
41
|
+
'ctrip-flight': 'https://flights.ctrip.com/',
|
|
42
|
+
'ctrip-hotel': 'https://hotels.ctrip.com/',
|
|
43
|
+
'train-12306': 'https://kyfw.12306.cn/',
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
let activePort = null
|
|
@@ -112,8 +130,9 @@ async function handleJob(job) {
|
|
|
112
130
|
return
|
|
113
131
|
}
|
|
114
132
|
if (job.kind === 'search') {
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
const allowedPrefix = SITE_SEARCH_PREFIXES[site]
|
|
134
|
+
if (!allowedPrefix || typeof job.url !== 'string' || !job.url.startsWith(allowedPrefix)) {
|
|
135
|
+
await postResult(jobId, { ok: false, error: `search 只允许 ${allowedPrefix ?? '已注册站点域'}(收到 ${String(job.url).slice(0, 80)})` })
|
|
117
136
|
return
|
|
118
137
|
}
|
|
119
138
|
const tab = await chrome.tabs.create({ url: job.url, active: false })
|
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
* GoTry Session Bridge — MAIN-world 网络嗅探(manifest world:"MAIN",document_start)。
|
|
3
3
|
*
|
|
4
4
|
* MAIN world 没有 chrome.runtime,只负责:hook 站点自己的 fetch/XHR,把命中
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 嗅探面的响应文本以 CustomEvent 派发到页面里;ISOLATED 侧的 content-bridge.js
|
|
6
|
+
* 监听事件并经 chrome.runtime 转发给 Service Worker。
|
|
7
|
+
*
|
|
8
|
+
* 嗅探面(与 Node 侧 adapters/ctrip-flight.ts NETWORK_HINTS、adapters/ctrip-hotel.ts
|
|
9
|
+
* HOTEL_NETWORK_HINTS、adapters/rail-12306.ts TRAIN_NETWORK_HINTS 对账,run-all §38
|
|
10
|
+
* 防漂移断言守住):
|
|
11
|
+
* - 机票:search/api/search/batchSearch(精确接口)
|
|
12
|
+
* - 酒店(2026-09-03 实装):URL hint 多版接口名 + **形状嗅探兜底**——响应体
|
|
13
|
+
* JSON 含酒店清单签名即转发,对接口改名免疫(接口名公开资料多版并存,首个
|
|
14
|
+
* 真会话后校准,D-13 同款边界)
|
|
7
15
|
*
|
|
8
16
|
* 只读被动转发:不修改请求、不重放、不伪造(§2.3 反模式红线)——响应由站点自己的
|
|
9
17
|
* 代码发出,我们只听。
|
|
@@ -12,8 +20,15 @@
|
|
|
12
20
|
if (window.__gotrySniffInstalled) return
|
|
13
21
|
window.__gotrySniffInstalled = true
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
var
|
|
23
|
+
var FLIGHT_HINT_RE = /search\/api\/search\/batchSearch/
|
|
24
|
+
var HOTEL_HINT_RE = /hotels\.ctrip\.com\/(hotels\/api|domestic\/pc\/api)|GetHotelListBySOA|GetHotelListByCity|HotelSearch|hotelsearch|restapi\/soa2\/\d+\/fetchHotelList/i
|
|
25
|
+
/** 火车(2026-09-03 实装):12306 余票查询 XHR(负载均衡变体 queryG/Z/A/U 全命中) */
|
|
26
|
+
var TRAIN_HINT_RE = /leftTicket\/query/i
|
|
27
|
+
/** 形状嗅探(酒店页兜底;与 Node 侧 looksLikeHotelListBody 签名一致) */
|
|
28
|
+
var HOTEL_BODY_SIG_RE = /"hotelList"|"hotelMatchInfos"|"hotelName"/
|
|
29
|
+
var HOTEL_BODY_MAX = 2_000_000
|
|
30
|
+
var isHotelPage = /(^|\.)hotels\.ctrip\.com$/.test(location.hostname)
|
|
31
|
+
var isTrainPage = /(^|\.)12306\.cn$/.test(location.hostname)
|
|
17
32
|
|
|
18
33
|
function dispatch(url, body) {
|
|
19
34
|
try {
|
|
@@ -21,6 +36,13 @@
|
|
|
21
36
|
} catch { /* 页面环境异常不抛 */ }
|
|
22
37
|
}
|
|
23
38
|
|
|
39
|
+
function hotelPageWants(url) {
|
|
40
|
+
return isHotelPage && (HOTEL_HINT_RE.test(url) || FLIGHT_HINT_RE.test(url))
|
|
41
|
+
}
|
|
42
|
+
function trainPageWants(url) {
|
|
43
|
+
return isTrainPage && TRAIN_HINT_RE.test(url)
|
|
44
|
+
}
|
|
45
|
+
|
|
24
46
|
var origFetch = window.fetch
|
|
25
47
|
if (typeof origFetch === 'function') {
|
|
26
48
|
window.fetch = function (input, init) {
|
|
@@ -29,9 +51,21 @@
|
|
|
29
51
|
var url = ''
|
|
30
52
|
if (typeof input === 'string') url = input
|
|
31
53
|
else if (input && typeof input.url === 'string') url = input.url
|
|
32
|
-
|
|
54
|
+
var urlHint = FLIGHT_HINT_RE.test(url) || hotelPageWants(url) || trainPageWants(url)
|
|
55
|
+
var isPost = false
|
|
56
|
+
try { isPost = String((init && init.method) || (input && input.method) || 'GET').toUpperCase() === 'POST' } catch { /* ignore */ }
|
|
57
|
+
var capOk = true
|
|
58
|
+
try {
|
|
59
|
+
var len = Number(res.headers && res.headers.get('content-length'))
|
|
60
|
+
if (isHotelPage && !urlHint && len > HOTEL_BODY_MAX) capOk = false
|
|
61
|
+
} catch { /* 头不可读则放行小体兜底 */ }
|
|
62
|
+
if (urlHint || (isHotelPage && isPost && capOk)) {
|
|
33
63
|
var clone = res.clone()
|
|
34
|
-
clone.text().then(function (t) {
|
|
64
|
+
clone.text().then(function (t) {
|
|
65
|
+
try {
|
|
66
|
+
if (urlHint || (t.length <= HOTEL_BODY_MAX && HOTEL_BODY_SIG_RE.test(t))) dispatch(url, t)
|
|
67
|
+
} catch { /* 嗅探失败不影响站点自身 */ }
|
|
68
|
+
}).catch(function () { /* 流不可读则跳过 */ })
|
|
35
69
|
}
|
|
36
70
|
} catch { /* 嗅探失败不影响站点自身 */ }
|
|
37
71
|
return res
|
|
@@ -43,20 +77,24 @@
|
|
|
43
77
|
var origSend = XMLHttpRequest.prototype.send
|
|
44
78
|
XMLHttpRequest.prototype.open = function (method, url) {
|
|
45
79
|
this.__gotryUrl = typeof url === 'string' ? url : String(url ?? '')
|
|
80
|
+
this.__gotryMethod = typeof method === 'string' ? method.toUpperCase() : 'GET'
|
|
46
81
|
return origOpen.apply(this, arguments)
|
|
47
82
|
}
|
|
48
83
|
XMLHttpRequest.prototype.send = function () {
|
|
49
84
|
var xhr = this
|
|
50
85
|
xhr.addEventListener('load', function () {
|
|
51
86
|
try {
|
|
52
|
-
if (!xhr.__gotryUrl
|
|
87
|
+
if (!xhr.__gotryUrl) return
|
|
88
|
+
var urlHint = FLIGHT_HINT_RE.test(xhr.__gotryUrl) || hotelPageWants(xhr.__gotryUrl) || trainPageWants(xhr.__gotryUrl)
|
|
89
|
+
if (!urlHint && !(isHotelPage && xhr.__gotryMethod === 'POST')) return
|
|
53
90
|
var body = ''
|
|
54
91
|
if (xhr.responseType === '' || xhr.responseType === 'text') body = xhr.responseText
|
|
55
92
|
else if (xhr.responseType === 'json') body = JSON.stringify(xhr.response)
|
|
56
93
|
else return
|
|
57
|
-
if (body)
|
|
94
|
+
if (!body) return
|
|
95
|
+
if (urlHint || (body.length <= HOTEL_BODY_MAX && HOTEL_BODY_SIG_RE.test(body))) dispatch(xhr.__gotryUrl, body)
|
|
58
96
|
} catch { /* 嗅探失败不影响站点 */ }
|
|
59
97
|
})
|
|
60
98
|
return origSend.apply(this, arguments)
|
|
61
99
|
}
|
|
62
|
-
})()
|
|
100
|
+
})()
|
package/extension/manifest.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "GoTry Session Bridge",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"description": "GoTry 会话检索桥(issue #21)
|
|
4
|
+
"version": "0.0.1.19",
|
|
5
|
+
"description": "GoTry 会话检索桥(issue #21):在你自己的登录态里只读嗅探检索回包(机票/酒店/火车)与登录票据 cookie 名;零凭证经手,登录在官网由你完成,检索只读不写。",
|
|
6
6
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnuzBX8rT8+RZKEUip5VRA0cY056pExy9PNdN9YCiuY3dgglRbcIBhjuFJoR6EX3/MaUv82vJVyItRVrowXLTwDk4WZof0vwFgKL0zBK304PC46pUaAKTSu1PgXeL1+j5KTAGz/9a9+RnPdbj7jND5rM/MOmBHNPVPpp1NlwxRGIgF9OxHSOfXORcH4Iyte1aJSFUJbFtNS/DaE5D+KjyupPLyIqVN/dCiKnNsTCEHQB9ngeli665PFp76hkccHQQZ4sVuMLS4zBPcazUsp1kcnW+xsQoUATkODTCY+Pzk3zUVF1QOESTjRCVnbNi8tkO43RMRs2q63gX/3inFivcEwIDAQAB",
|
|
7
|
-
"permissions": [
|
|
7
|
+
"permissions": [
|
|
8
|
+
"cookies",
|
|
9
|
+
"alarms"
|
|
10
|
+
],
|
|
8
11
|
"host_permissions": [
|
|
9
12
|
"http://127.0.0.1:8791/*",
|
|
10
13
|
"http://127.0.0.1:8792/*",
|
|
@@ -18,15 +21,30 @@
|
|
|
18
21
|
},
|
|
19
22
|
"content_scripts": [
|
|
20
23
|
{
|
|
21
|
-
"matches": [
|
|
22
|
-
|
|
24
|
+
"matches": [
|
|
25
|
+
"https://flights.ctrip.com/*",
|
|
26
|
+
"https://hotels.ctrip.com/*",
|
|
27
|
+
"https://kyfw.12306.cn/*",
|
|
28
|
+
"https://www.12306.cn/*"
|
|
29
|
+
],
|
|
30
|
+
"js": [
|
|
31
|
+
"content-main.js"
|
|
32
|
+
],
|
|
23
33
|
"run_at": "document_start",
|
|
24
34
|
"world": "MAIN"
|
|
25
35
|
},
|
|
26
36
|
{
|
|
27
|
-
"matches": [
|
|
28
|
-
|
|
37
|
+
"matches": [
|
|
38
|
+
"https://flights.ctrip.com/*",
|
|
39
|
+
"https://hotels.ctrip.com/*",
|
|
40
|
+
"https://kyfw.12306.cn/*",
|
|
41
|
+
"https://www.12306.cn/*"
|
|
42
|
+
],
|
|
43
|
+
"js": [
|
|
44
|
+
"content-bridge.js"
|
|
45
|
+
],
|
|
29
46
|
"run_at": "document_start"
|
|
30
47
|
}
|
|
31
|
-
]
|
|
32
|
-
|
|
48
|
+
],
|
|
49
|
+
"version_name": "0.0.1-rc.19"
|
|
50
|
+
}
|