@osovv/vv-opencode 1.1.6 → 1.2.1

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.
Files changed (127) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +59 -2
  3. package/dist/commands/completion.js +1 -1
  4. package/dist/commands/completion.js.map +1 -1
  5. package/dist/commands/init.js +1 -1
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/install.js +7 -2
  8. package/dist/commands/install.js.map +1 -1
  9. package/dist/commands/patch-provider.d.ts +4 -4
  10. package/dist/commands/patch-provider.js +7 -6
  11. package/dist/commands/patch-provider.js.map +1 -1
  12. package/dist/commands/plugin-list.js +1 -1
  13. package/dist/commands/plugin-list.js.map +1 -1
  14. package/dist/commands/plugin-toggle.js +1 -1
  15. package/dist/commands/plugin-toggle.js.map +1 -1
  16. package/dist/commands/preset.js +1 -0
  17. package/dist/commands/preset.js.map +1 -1
  18. package/dist/commands/role.js +1 -1
  19. package/dist/commands/role.js.map +1 -1
  20. package/dist/commands/sync.js +7 -2
  21. package/dist/commands/sync.js.map +1 -1
  22. package/dist/commands/upgrade.js +1 -1
  23. package/dist/commands/upgrade.js.map +1 -1
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +7 -5
  26. package/dist/index.js.map +1 -1
  27. package/dist/lib/agent-models.js +1 -1
  28. package/dist/lib/agent-models.js.map +1 -1
  29. package/dist/lib/managed-agents.js +1 -1
  30. package/dist/lib/managed-agents.js.map +1 -1
  31. package/dist/lib/managed-skills.js +1 -1
  32. package/dist/lib/managed-skills.js.map +1 -1
  33. package/dist/lib/model-roles.js +1 -1
  34. package/dist/lib/model-roles.js.map +1 -1
  35. package/dist/lib/opencode.js +22 -7
  36. package/dist/lib/opencode.js.map +1 -1
  37. package/dist/lib/orchestration.js +4 -2
  38. package/dist/lib/orchestration.js.map +1 -1
  39. package/dist/lib/plugin-toggle-config.d.ts +1 -1
  40. package/dist/lib/plugin-toggle-config.js +4 -2
  41. package/dist/lib/plugin-toggle-config.js.map +1 -1
  42. package/dist/lib/vvoc-config.d.ts +93 -0
  43. package/dist/lib/vvoc-config.js +87 -2
  44. package/dist/lib/vvoc-config.js.map +1 -1
  45. package/dist/lib/vvoc-paths.js +2 -3
  46. package/dist/lib/vvoc-paths.js.map +1 -1
  47. package/dist/lib/vvoc-preset-registry.js +1 -1
  48. package/dist/lib/vvoc-preset-registry.js.map +1 -1
  49. package/dist/plugins/guardian/index.js +44 -6
  50. package/dist/plugins/guardian/index.js.map +1 -1
  51. package/dist/plugins/hashline-edit/edit-text-normalization.js +6 -5
  52. package/dist/plugins/hashline-edit/edit-text-normalization.js.map +1 -1
  53. package/dist/plugins/hashline-edit/index.js +16 -7
  54. package/dist/plugins/hashline-edit/index.js.map +1 -1
  55. package/dist/plugins/hashline-edit/tool-description.d.ts +1 -1
  56. package/dist/plugins/hashline-edit/tool-description.js +3 -2
  57. package/dist/plugins/hashline-edit/tool-description.js.map +1 -1
  58. package/dist/plugins/secrets-redaction/config.d.ts +6 -1
  59. package/dist/plugins/secrets-redaction/config.js +30 -5
  60. package/dist/plugins/secrets-redaction/config.js.map +1 -1
  61. package/dist/plugins/secrets-redaction/deep.js +1 -1
  62. package/dist/plugins/secrets-redaction/deep.js.map +1 -1
  63. package/dist/plugins/secrets-redaction/engine.js +10 -4
  64. package/dist/plugins/secrets-redaction/engine.js.map +1 -1
  65. package/dist/plugins/secrets-redaction/index.js +14 -21
  66. package/dist/plugins/secrets-redaction/index.js.map +1 -1
  67. package/dist/plugins/secrets-redaction/patterns.js +4 -4
  68. package/dist/plugins/secrets-redaction/patterns.js.map +1 -1
  69. package/dist/plugins/secrets-redaction/restore.js +1 -1
  70. package/dist/plugins/secrets-redaction/restore.js.map +1 -1
  71. package/dist/plugins/secrets-redaction/session.js +1 -2
  72. package/dist/plugins/secrets-redaction/session.js.map +1 -1
  73. package/dist/plugins/web-tools/config.d.ts +43 -0
  74. package/dist/plugins/web-tools/config.js +134 -0
  75. package/dist/plugins/web-tools/config.js.map +1 -0
  76. package/dist/plugins/web-tools/fetch-service.d.ts +14 -0
  77. package/dist/plugins/web-tools/fetch-service.js +169 -0
  78. package/dist/plugins/web-tools/fetch-service.js.map +1 -0
  79. package/dist/plugins/web-tools/html-markdown.d.ts +13 -0
  80. package/dist/plugins/web-tools/html-markdown.js +82 -0
  81. package/dist/plugins/web-tools/html-markdown.js.map +1 -0
  82. package/dist/plugins/web-tools/http.d.ts +34 -0
  83. package/dist/plugins/web-tools/http.js +154 -0
  84. package/dist/plugins/web-tools/http.js.map +1 -0
  85. package/dist/plugins/web-tools/index.d.ts +18 -0
  86. package/dist/plugins/web-tools/index.js +93 -0
  87. package/dist/plugins/web-tools/index.js.map +1 -0
  88. package/dist/plugins/web-tools/media-loader.d.ts +26 -0
  89. package/dist/plugins/web-tools/media-loader.js +178 -0
  90. package/dist/plugins/web-tools/media-loader.js.map +1 -0
  91. package/dist/plugins/web-tools/providers/brave.d.ts +3 -0
  92. package/dist/plugins/web-tools/providers/brave.js +101 -0
  93. package/dist/plugins/web-tools/providers/brave.js.map +1 -0
  94. package/dist/plugins/web-tools/providers/exa.d.ts +28 -0
  95. package/dist/plugins/web-tools/providers/exa.js +138 -0
  96. package/dist/plugins/web-tools/providers/exa.js.map +1 -0
  97. package/dist/plugins/web-tools/providers/native-fetch.d.ts +21 -0
  98. package/dist/plugins/web-tools/providers/native-fetch.js +112 -0
  99. package/dist/plugins/web-tools/providers/native-fetch.js.map +1 -0
  100. package/dist/plugins/web-tools/providers/spider.d.ts +25 -0
  101. package/dist/plugins/web-tools/providers/spider.js +104 -0
  102. package/dist/plugins/web-tools/providers/spider.js.map +1 -0
  103. package/dist/plugins/web-tools/providers/zai.d.ts +33 -0
  104. package/dist/plugins/web-tools/providers/zai.js +181 -0
  105. package/dist/plugins/web-tools/providers/zai.js.map +1 -0
  106. package/dist/plugins/web-tools/search-service.d.ts +18 -0
  107. package/dist/plugins/web-tools/search-service.js +110 -0
  108. package/dist/plugins/web-tools/search-service.js.map +1 -0
  109. package/dist/plugins/workflow/index.js +24 -5
  110. package/dist/plugins/workflow/index.js.map +1 -1
  111. package/dist/plugins/workflow/persistence.js +1 -1
  112. package/dist/plugins/workflow/persistence.js.map +1 -1
  113. package/dist/plugins/workflow/state.d.ts +14 -0
  114. package/dist/plugins/workflow/state.js +44 -1
  115. package/dist/plugins/workflow/state.js.map +1 -1
  116. package/dist/plugins/workflow/tooling.js +1 -1
  117. package/dist/plugins/workflow/tooling.js.map +1 -1
  118. package/dist/tui/context/analyze.js +11 -3
  119. package/dist/tui/context/analyze.js.map +1 -1
  120. package/dist/tui/context/collect.js +1 -1
  121. package/dist/tui/context/collect.js.map +1 -1
  122. package/dist/tui/context/plugin.js +3 -2
  123. package/dist/tui/context/plugin.js.map +1 -1
  124. package/dist/tui.js +1 -1
  125. package/package.json +15 -8
  126. package/schemas/vvoc/v3.json +43 -1
  127. package/templates/skills/vv-execute/SKILL.md +17 -12
@@ -0,0 +1,104 @@
1
+ // FILE: src/plugins/web-tools/providers/spider.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Call the Spider.cloud Scrape API for one exact URL and serve binary targets through the shared direct media loader.
5
+ // SCOPE: Bearer auth, mapped return_format, array envelope validation, status and duration metadata, provider error mapping, and media-first dispatch.
6
+ // DEPENDS: [src/plugins/web-tools/http.ts, src/plugins/web-tools/config.ts, src/plugins/web-tools/media-loader.ts, src/plugins/web-tools/providers/exa.ts, @opencode-ai/plugin]
7
+ // LINKS: [M-WEB-SPIDER, M-WEB-HTTP, M-WEB-MEDIA-LOADER, M-WEB-FETCH-SERVICE]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // SpiderOutcome - Text or media outcome of a Spider scrape.
14
+ // scrapeSpider - Serve media targets directly, otherwise scrape one URL through Spider.
15
+ // END_MODULE_MAP
16
+ //
17
+ // START_CHANGE_SUMMARY
18
+ // LAST_CHANGE: [v1.0.0 - Initial Spider Scrape adapter.]
19
+ // END_CHANGE_SUMMARY
20
+ import { DEFAULT_TEXT_MAX_BYTES, requestBounded, } from "../http.js";
21
+ import { loadMediaAttachment, WebMediaError } from "../media-loader.js";
22
+ import { mapHttpErrorToProviderError, WebProviderError } from "./exa.js";
23
+ const SPIDER_SCRAPE_URL = "https://api.spider.cloud/scrape";
24
+ const SPIDER_RETURN_FORMAT = { markdown: "markdown", text: "text", html: "raw" };
25
+ // START_BLOCK_HELPERS
26
+ function isPlainObject(value) {
27
+ return typeof value === "object" && value !== null && !Array.isArray(value);
28
+ }
29
+ function parseSpiderResponse(bytes) {
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(new TextDecoder().decode(bytes));
33
+ }
34
+ catch {
35
+ throw new WebProviderError("spider", "BAD_RESPONSE", "malformed JSON response from spider");
36
+ }
37
+ if (!Array.isArray(parsed) || parsed.length === 0) {
38
+ throw new WebProviderError("spider", "BAD_RESPONSE", "unexpected spider response envelope");
39
+ }
40
+ const entry = parsed[0];
41
+ if (!isPlainObject(entry)) {
42
+ throw new WebProviderError("spider", "BAD_RESPONSE", "unexpected spider response envelope");
43
+ }
44
+ const status = typeof entry.status === "number" ? entry.status : undefined;
45
+ const duration = typeof entry.duration === "number" ? entry.duration : undefined;
46
+ const metadata = {
47
+ ...(status !== undefined ? { status } : {}),
48
+ ...(duration !== undefined ? { durationMs: duration } : {}),
49
+ };
50
+ if (entry.error) {
51
+ const detail = typeof entry.error === "string" ? entry.error : "scrape failed";
52
+ throw new WebProviderError("spider", "PROVIDER_ERROR", `spider error: ${detail}`, status);
53
+ }
54
+ const content = typeof entry.content === "string" ? entry.content : undefined;
55
+ if (content === undefined) {
56
+ throw new WebProviderError("spider", "BAD_RESPONSE", "spider response missing content");
57
+ }
58
+ return { kind: "text", content, metadata };
59
+ }
60
+ // END_BLOCK_HELPERS
61
+ // START_CONTRACT: scrapeSpider
62
+ // PURPOSE: Serve image and PDF targets through the shared media loader; scrape textual targets through Spider.
63
+ // INPUTS: { input: url, format, credential, abort, timeoutMs; fetchImpl: injectable fetch }
64
+ // OUTPUTS: { SpiderOutcome - text or media with provider metadata }
65
+ // SIDE_EFFECTS: performs network I/O through fetchImpl; never logs the credential
66
+ // LINKS: M-WEB-FETCH-SERVICE
67
+ // END_CONTRACT: scrapeSpider
68
+ export async function scrapeSpider(input, fetchImpl) {
69
+ try {
70
+ const attachment = await loadMediaAttachment({ url: input.url, abort: input.abort, timeoutMs: input.timeoutMs }, fetchImpl);
71
+ return { kind: "media", attachment, metadata: {} };
72
+ }
73
+ catch (error) {
74
+ if (!(error instanceof WebMediaError)) {
75
+ throw error;
76
+ }
77
+ }
78
+ const body = JSON.stringify({
79
+ url: input.url,
80
+ return_format: [SPIDER_RETURN_FORMAT[input.format]],
81
+ });
82
+ let response;
83
+ try {
84
+ response = await requestBounded({
85
+ url: SPIDER_SCRAPE_URL,
86
+ method: "POST",
87
+ headers: {
88
+ Authorization: `Bearer ${input.credential.value}`,
89
+ "content-type": "application/json",
90
+ accept: "application/json",
91
+ },
92
+ body,
93
+ timeoutMs: input.timeoutMs,
94
+ maxBytes: DEFAULT_TEXT_MAX_BYTES,
95
+ abort: input.abort,
96
+ }, fetchImpl);
97
+ }
98
+ catch (error) {
99
+ mapHttpErrorToProviderError(error, "spider");
100
+ throw error;
101
+ }
102
+ return parseSpiderResponse(response.bytes);
103
+ }
104
+ //# sourceMappingURL=spider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spider.js","sourceRoot":"","sources":["../../../../src/plugins/web-tools/providers/spider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { ToolAttachment } from "@opencode-ai/plugin";
2
+ import type { VvocWebRegion } from "../../../lib/vvoc-config.js";
3
+ import type { WebProviderCredential } from "../config.js";
4
+ import { type FetchLike } from "../http.js";
5
+ import { type WebSearchInput, type WebSearchResult } from "./exa.js";
6
+ type FetchFormat = "markdown" | "text" | "html";
7
+ export type ZaiReaderOutcome = {
8
+ kind: "text";
9
+ content: string;
10
+ metadata: {
11
+ status: number;
12
+ requestId?: string;
13
+ model?: string;
14
+ created?: number;
15
+ title?: string;
16
+ };
17
+ } | {
18
+ kind: "media";
19
+ attachment: ToolAttachment;
20
+ metadata: Record<string, never>;
21
+ };
22
+ export declare function searchZai(input: WebSearchInput & {
23
+ region: VvocWebRegion;
24
+ }, fetchImpl?: FetchLike): Promise<WebSearchResult[]>;
25
+ export declare function fetchZai(input: {
26
+ url: string;
27
+ format: FetchFormat;
28
+ region: VvocWebRegion;
29
+ credential: WebProviderCredential;
30
+ abort: AbortSignal;
31
+ timeoutMs: number;
32
+ }, fetchImpl?: FetchLike): Promise<ZaiReaderOutcome>;
33
+ export {};
@@ -0,0 +1,181 @@
1
+ // FILE: src/plugins/web-tools/providers/zai.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Call the documented Z.AI international or Zhipu China Tool API directly for provider-neutral web search and reader extraction.
5
+ // SCOPE: Explicit region endpoint selection, bearer-authenticated search and reader requests, response normalization, direct media-first fetch dispatch, and provider error mapping without MCP behavior.
6
+ // DEPENDS: [src/plugins/web-tools/config.ts, src/plugins/web-tools/http.ts, src/plugins/web-tools/media-loader.ts, src/plugins/web-tools/providers/exa.ts, @opencode-ai/plugin]
7
+ // LINKS: [M-WEB-ZAI, M-WEB-HTTP, M-WEB-MEDIA-LOADER, M-WEB-SEARCH-SERVICE, M-WEB-FETCH-SERVICE]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // ZaiReaderOutcome - Text or direct media result returned by the Z.AI fetch adapter.
14
+ // searchZai - Execute one direct regional Z.AI or Zhipu web search request.
15
+ // fetchZai - Return supported media directly or read textual content through the regional reader endpoint.
16
+ // END_MODULE_MAP
17
+ //
18
+ // START_CHANGE_SUMMARY
19
+ // LAST_CHANGE: [C-ZAI-DIRECT-WEB-PROVIDERS - Added direct international and China search and reader Tool API integration.]
20
+ // END_CHANGE_SUMMARY
21
+ import { DEFAULT_TEXT_MAX_BYTES, requestBounded, } from "../http.js";
22
+ import { loadMediaAttachment, WebMediaError } from "../media-loader.js";
23
+ import { mapHttpErrorToProviderError, WebProviderError, } from "./exa.js";
24
+ const REGION_CONFIG = {
25
+ international: {
26
+ searchUrl: "https://api.z.ai/api/paas/v4/web_search",
27
+ readerUrl: "https://api.z.ai/api/paas/v4/reader",
28
+ searchEngine: "search-prime",
29
+ },
30
+ china: {
31
+ searchUrl: "https://open.bigmodel.cn/api/paas/v4/web_search",
32
+ readerUrl: "https://open.bigmodel.cn/api/paas/v4/reader",
33
+ searchEngine: "search_pro",
34
+ },
35
+ };
36
+ const ZAI_FRESHNESS = {
37
+ day: "oneDay",
38
+ week: "oneWeek",
39
+ month: "oneMonth",
40
+ year: "oneYear",
41
+ };
42
+ // START_BLOCK_RESPONSE_HELPERS
43
+ function isPlainObject(value) {
44
+ return typeof value === "object" && value !== null && !Array.isArray(value);
45
+ }
46
+ function decodeJson(bytes, operation) {
47
+ try {
48
+ return JSON.parse(new TextDecoder().decode(bytes));
49
+ }
50
+ catch {
51
+ throw new WebProviderError("zai", "BAD_RESPONSE", `malformed JSON response from zai ${operation}`);
52
+ }
53
+ }
54
+ function normalizeSearchResult(item) {
55
+ if (!isPlainObject(item) || typeof item.link !== "string" || item.link.length === 0) {
56
+ return undefined;
57
+ }
58
+ const title = typeof item.title === "string" && item.title.length > 0 ? item.title : item.link;
59
+ const snippet = typeof item.content === "string" && item.content.length > 0 ? item.content : undefined;
60
+ const publishedAt = typeof item.publish_date === "string" && item.publish_date.length > 0
61
+ ? item.publish_date
62
+ : undefined;
63
+ return {
64
+ title,
65
+ url: item.link,
66
+ ...(snippet ? { snippet } : {}),
67
+ ...(publishedAt ? { publishedAt } : {}),
68
+ };
69
+ }
70
+ function parseSearchResponse(bytes) {
71
+ const parsed = decodeJson(bytes, "search");
72
+ if (!isPlainObject(parsed) || !Array.isArray(parsed.search_result)) {
73
+ throw new WebProviderError("zai", "BAD_RESPONSE", "unexpected zai search response envelope");
74
+ }
75
+ return parsed.search_result
76
+ .map(normalizeSearchResult)
77
+ .filter((result) => result !== undefined);
78
+ }
79
+ function parseReaderResponse(response) {
80
+ const parsed = decodeJson(response.bytes, "reader");
81
+ const envelope = isPlainObject(parsed) ? parsed : undefined;
82
+ const readerResult = envelope?.reader_result;
83
+ if (!isPlainObject(readerResult) || typeof readerResult.content !== "string") {
84
+ throw new WebProviderError("zai", "BAD_RESPONSE", "unexpected zai reader response envelope");
85
+ }
86
+ return {
87
+ kind: "text",
88
+ content: readerResult.content,
89
+ metadata: {
90
+ status: response.status,
91
+ ...(typeof envelope?.request_id === "string" ? { requestId: envelope.request_id } : {}),
92
+ ...(typeof envelope?.model === "string" ? { model: envelope.model } : {}),
93
+ ...(typeof envelope?.created === "number" ? { created: envelope.created } : {}),
94
+ ...(typeof readerResult.title === "string" ? { title: readerResult.title } : {}),
95
+ },
96
+ };
97
+ }
98
+ // END_BLOCK_RESPONSE_HELPERS
99
+ // START_CONTRACT: searchZai
100
+ // PURPOSE: Search through the documented direct Z.AI or Zhipu Tool API for one explicit region.
101
+ // INPUTS: { input: provider-neutral search input plus region; fetchImpl: injectable fetch }
102
+ // OUTPUTS: { WebSearchResult[] - normalized provider-ranked results }
103
+ // SIDE_EFFECTS: performs one bounded authenticated HTTP request; never logs credentials
104
+ // LINKS: M-WEB-SEARCH-SERVICE
105
+ // END_CONTRACT: searchZai
106
+ export async function searchZai(input, fetchImpl) {
107
+ const regional = REGION_CONFIG[input.region];
108
+ const body = JSON.stringify({
109
+ search_engine: regional.searchEngine,
110
+ search_query: input.query,
111
+ count: input.count,
112
+ ...(input.freshness ? { search_recency_filter: ZAI_FRESHNESS[input.freshness] } : {}),
113
+ });
114
+ let response;
115
+ try {
116
+ response = await requestBounded({
117
+ url: regional.searchUrl,
118
+ method: "POST",
119
+ headers: {
120
+ Authorization: `Bearer ${input.credential.value}`,
121
+ "content-type": "application/json",
122
+ accept: "application/json",
123
+ },
124
+ body,
125
+ timeoutMs: input.timeoutMs,
126
+ maxBytes: DEFAULT_TEXT_MAX_BYTES,
127
+ abort: input.abort,
128
+ }, fetchImpl);
129
+ }
130
+ catch (error) {
131
+ mapHttpErrorToProviderError(error, "zai");
132
+ throw error;
133
+ }
134
+ return parseSearchResponse(response.bytes);
135
+ }
136
+ // START_CONTRACT: fetchZai
137
+ // PURPOSE: Return supported direct media attachments or read textual content through the documented regional reader Tool API.
138
+ // INPUTS: { input: url, format, region, credential, abort, timeoutMs; fetchImpl: injectable fetch }
139
+ // OUTPUTS: { ZaiReaderOutcome - direct media or normalized reader text and metadata }
140
+ // SIDE_EFFECTS: performs bounded network I/O; textual targets use a direct media probe before the reader API
141
+ // LINKS: M-WEB-FETCH-SERVICE, M-WEB-MEDIA-LOADER
142
+ // END_CONTRACT: fetchZai
143
+ export async function fetchZai(input, fetchImpl) {
144
+ try {
145
+ const attachment = await loadMediaAttachment({ url: input.url, abort: input.abort, timeoutMs: input.timeoutMs }, fetchImpl);
146
+ return { kind: "media", attachment, metadata: {} };
147
+ }
148
+ catch (error) {
149
+ if (!(error instanceof WebMediaError)) {
150
+ throw error;
151
+ }
152
+ }
153
+ const regional = REGION_CONFIG[input.region];
154
+ const body = JSON.stringify({
155
+ url: input.url,
156
+ timeout: input.timeoutMs / 1000,
157
+ return_format: input.format,
158
+ });
159
+ let response;
160
+ try {
161
+ response = await requestBounded({
162
+ url: regional.readerUrl,
163
+ method: "POST",
164
+ headers: {
165
+ Authorization: `Bearer ${input.credential.value}`,
166
+ "content-type": "application/json",
167
+ accept: "application/json",
168
+ },
169
+ body,
170
+ timeoutMs: input.timeoutMs,
171
+ maxBytes: DEFAULT_TEXT_MAX_BYTES,
172
+ abort: input.abort,
173
+ }, fetchImpl);
174
+ }
175
+ catch (error) {
176
+ mapHttpErrorToProviderError(error, "zai");
177
+ throw error;
178
+ }
179
+ return parseReaderResponse(response);
180
+ }
181
+ //# sourceMappingURL=zai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zai.js","sourceRoot":"","sources":["../../../../src/plugins/web-tools/providers/zai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type ToolDefinition } from "@opencode-ai/plugin";
2
+ import type { ResolvedWebSearchConfig } from "./config.js";
3
+ import { type WebSearchResult } from "./providers/exa.js";
4
+ /**
5
+ * Render ranked results as Markdown: numbered entries with [title](url),
6
+ * an indented snippet line when present, and an italic publishedAt line when present.
7
+ * Empty results render a short no-results notice.
8
+ */
9
+ export declare function renderSearchMarkdown(results: WebSearchResult[]): string;
10
+ /**
11
+ * Create the web_search tool bound to the resolved search configuration.
12
+ * execute asks permission key web_search with patterns [query], validates the credential
13
+ * at execution time, dispatches to exa, brave, or direct zai, and returns a ToolResult with
14
+ * title, Markdown output, and metadata { provider, region?, resultCount, credentialSource }.
15
+ * Missing credentials raise an actionable error naming the environment variable
16
+ * and the web.search.apiKey config field without printing any value.
17
+ */
18
+ export declare function createWebSearchTool(resolved: ResolvedWebSearchConfig): ToolDefinition;
@@ -0,0 +1,110 @@
1
+ // FILE: src/plugins/web-tools/search-service.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Build the provider-neutral web_search tool: input validation, permission request, provider dispatch, and ranked Markdown rendering.
5
+ // SCOPE: web_search ToolDefinition factory and Markdown rendering; delegates transport to the Exa, Brave, and direct Z.AI adapters.
6
+ // DEPENDS: [@opencode-ai/plugin, src/plugins/web-tools/config.ts, src/plugins/web-tools/http.ts, src/plugins/web-tools/providers/exa.ts, src/plugins/web-tools/providers/brave.ts, src/plugins/web-tools/providers/zai.ts]
7
+ // LINKS: [M-WEB-SEARCH-SERVICE, M-WEB-EXA, M-WEB-BRAVE, M-WEB-ZAI, M-WEB-HTTP, M-PLUGIN-WEB-TOOLS]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // renderSearchMarkdown - Render ranked search results as Markdown.
14
+ // createWebSearchTool - Create the web_search ToolDefinition bound to a resolved search config.
15
+ // END_MODULE_MAP
16
+ //
17
+ // START_CHANGE_SUMMARY
18
+ // LAST_CHANGE: [C-ZAI-DIRECT-WEB-PROVIDERS - Routed canonical web_search calls through the explicit-region direct Z.AI adapter.]
19
+ // LAST_CHANGE: [v1.0.0 - Initial web_search tool service.]
20
+ // END_CHANGE_SUMMARY
21
+ import { tool } from "@opencode-ai/plugin";
22
+ import { DEFAULT_REQUEST_TIMEOUT_MS } from "./http.js";
23
+ import { searchBrave } from "./providers/brave.js";
24
+ import { searchExa, WebProviderError } from "./providers/exa.js";
25
+ import { searchZai } from "./providers/zai.js";
26
+ const z = tool.schema;
27
+ /**
28
+ * Render ranked results as Markdown: numbered entries with [title](url),
29
+ * an indented snippet line when present, and an italic publishedAt line when present.
30
+ * Empty results render a short no-results notice.
31
+ */
32
+ export function renderSearchMarkdown(results) {
33
+ if (results.length === 0) {
34
+ return "No results found.";
35
+ }
36
+ return results
37
+ .map((result, index) => {
38
+ const lines = [`${index + 1}. [${result.title}](${result.url})`];
39
+ if (result.snippet) {
40
+ lines.push(` ${result.snippet}`);
41
+ }
42
+ if (result.publishedAt) {
43
+ lines.push(` _${result.publishedAt}_`);
44
+ }
45
+ return lines.join("\n");
46
+ })
47
+ .join("\n");
48
+ }
49
+ /**
50
+ * Create the web_search tool bound to the resolved search configuration.
51
+ * execute asks permission key web_search with patterns [query], validates the credential
52
+ * at execution time, dispatches to exa, brave, or direct zai, and returns a ToolResult with
53
+ * title, Markdown output, and metadata { provider, region?, resultCount, credentialSource }.
54
+ * Missing credentials raise an actionable error naming the environment variable
55
+ * and the web.search.apiKey config field without printing any value.
56
+ */
57
+ export function createWebSearchTool(resolved) {
58
+ return tool({
59
+ description: "Search the web using the configured provider and return ranked results as Markdown. Use for discovering information; returns titles, URLs, snippets, and dates.",
60
+ args: {
61
+ query: z.string().min(1).describe("The search query."),
62
+ count: z
63
+ .number()
64
+ .int()
65
+ .min(1)
66
+ .max(20)
67
+ .default(8)
68
+ .describe("Number of results, 1 through 20, default 8."),
69
+ freshness: z
70
+ .enum(["day", "week", "month", "year"])
71
+ .optional()
72
+ .describe("Optional time window restricting results."),
73
+ },
74
+ async execute(args, context) {
75
+ await context.ask({
76
+ permission: "web_search",
77
+ patterns: [args.query],
78
+ always: [],
79
+ metadata: { provider: resolved.provider },
80
+ });
81
+ if (!resolved.credential) {
82
+ throw new WebProviderError(resolved.provider, "MISSING_CREDENTIAL", `missing credential for ${resolved.provider}: set ${resolved.envVar} or ${resolved.configField}`);
83
+ }
84
+ const searchInput = {
85
+ query: args.query,
86
+ count: args.count,
87
+ freshness: args.freshness,
88
+ credential: resolved.credential,
89
+ abort: context.abort,
90
+ timeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
91
+ };
92
+ const results = resolved.provider === "brave"
93
+ ? await searchBrave(searchInput)
94
+ : resolved.provider === "zai"
95
+ ? await searchZai({ ...searchInput, region: resolved.region })
96
+ : await searchExa(searchInput);
97
+ return {
98
+ title: `web_search: ${args.query}`,
99
+ output: renderSearchMarkdown(results),
100
+ metadata: {
101
+ provider: resolved.provider,
102
+ ...(resolved.provider === "zai" ? { region: resolved.region } : {}),
103
+ resultCount: results.length,
104
+ credentialSource: resolved.credential.source,
105
+ },
106
+ };
107
+ },
108
+ });
109
+ }
110
+ //# sourceMappingURL=search-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-service.js","sourceRoot":"","sources":["../../../src/plugins/web-tools/search-service.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  // FILE: src/plugins/workflow/index.ts
2
- // VERSION: 0.3.0
2
+ // VERSION: 0.5.1
3
3
  // START_MODULE_CONTRACT
4
4
  // PURPOSE: Register workflow tools and enforcement while injecting only startup-profile-compatible vv-controller guidance.
5
5
  // SCOPE: work_item_open/list/close registration, tracked launch validation, result normalization and repair, round aggregation with bounded excerpts, implementation round limits, persistence, and profile-selected chat.message guidance.
@@ -14,6 +14,8 @@
14
14
  // END_MODULE_MAP
15
15
  //
16
16
  // START_CHANGE_SUMMARY
17
+ // LAST_CHANGE: [v0.5.1 - Fixed session.deleted cleanup to read the session id from properties.info.id (per the SDK event shape), drop the in-memory store, and remove a duplicate directory deletion.]
18
+ // LAST_CHANGE: [v0.5.0 - Reverted in-flight reviewer launches on failed tracked results (invalid output, parse/repair failure, missing work item, apply failure) so work items are no longer stranded in REVIEWER_ALREADY_IN_FLIGHT.]
17
19
  // LAST_CHANGE: [v0.4.1 - Propagated bounded tracked-result excerpts through protocol errors, state-application errors, hard stops, and persisted work-item state.]
18
20
  // LAST_CHANGE: [v0.4.0 - Read plugin toggles from the shared startup vvoc config snapshot.]
19
21
  // LAST_CHANGE: [v0.3.0 - Integrated explicit work-item intent, reviewer in-flight tracking, and collect-all review-round result aggregation.]
@@ -31,7 +33,7 @@
31
33
  import { tool } from "@opencode-ai/plugin";
32
34
  import { attemptTrackedResultRepair, isTrackedResultRepairEligible, unwrapResumableTaskResult, } from "./repair.js";
33
35
  import { parseResultBlock, parseWorkItemHeader, TRACKED_SUBAGENT_NAMES, } from "./protocol.js";
34
- import { applyTrackedResult, createWorkflowResultExcerpt, beginTrackedLaunch, createWorkItemStore, getReviewRound, getWorkItem, } from "./state.js";
36
+ import { applyTrackedResult, createWorkflowResultExcerpt, beginTrackedLaunch, createWorkItemStore, getReviewRound, getWorkItem, revertReviewerLaunch, } from "./state.js";
35
37
  import { getAllowedNextAgents, getAttemptedImplementationRound, shouldBlockRound, } from "./transitions.js";
36
38
  import { createWorkItemCloseTool, createWorkItemListTool, createWorkItemOpenTool, } from "./tooling.js";
37
39
  import workflowSystemInstructionTemplate from "./system-instruction.md?raw";
@@ -189,7 +191,6 @@ function createHardStopMessage(options) {
189
191
  "Inspect work_item_list before retrying.",
190
192
  ].join("\n");
191
193
  }
192
- // START_BLOCK_PLUGIN_ENTRY
193
194
  export const WorkflowPlugin = async ({ client, directory }) => {
194
195
  const vvoc = await loadVvocConfig({ cwd: directory });
195
196
  if (!isVvocPluginEnabled(vvoc.config, "workflow"))
@@ -220,6 +221,7 @@ export const WorkflowPlugin = async ({ client, directory }) => {
220
221
  }
221
222
  }
222
223
  // END_BLOCK_PERSISTENCE_SETUP
224
+ // START_BLOCK_PLUGIN_ENTRY
223
225
  // Tool wrappers still need a store reference for description/args shape
224
226
  // but execute handlers resolve the right store per-call
225
227
  const dummyStore = createWorkItemStore();
@@ -416,6 +418,18 @@ export const WorkflowPlugin = async ({ client, directory }) => {
416
418
  });
417
419
  throw new Error(`RESULT_PROTOCOL_ERROR: ${header.error.message}`);
418
420
  }
421
+ // Revert an in-flight reviewer launch on any failure below so the work
422
+ // item is not permanently stranded in REVIEWER_ALREADY_IN_FLIGHT. No-op
423
+ // for implementer launches (they carry no in-flight reviewer slot).
424
+ const revertLaunch = () => {
425
+ const store = getOrCreateStore(input.sessionID);
426
+ revertReviewerLaunch(store, {
427
+ sessionId: input.sessionID,
428
+ workItemId: header.value,
429
+ agent: subagentType,
430
+ });
431
+ snapshotSession(input.sessionID);
432
+ };
419
433
  if (typeof output.output !== "string") {
420
434
  await client.app.log({
421
435
  body: {
@@ -429,6 +443,7 @@ export const WorkflowPlugin = async ({ client, directory }) => {
429
443
  },
430
444
  },
431
445
  });
446
+ revertLaunch();
432
447
  throw new Error("RESULT_PROTOCOL_ERROR: tracked task output must be a string");
433
448
  }
434
449
  const unwrapped = unwrapResumableTaskResult(output.output);
@@ -493,6 +508,7 @@ export const WorkflowPlugin = async ({ client, directory }) => {
493
508
  },
494
509
  },
495
510
  });
511
+ revertLaunch();
496
512
  throw new Error([
497
513
  `RESULT_PROTOCOL_ERROR: ${parsed.error.message}`,
498
514
  formatResultExcerptForError(protocolFailureExcerpt),
@@ -532,6 +548,7 @@ export const WorkflowPlugin = async ({ client, directory }) => {
532
548
  },
533
549
  },
534
550
  });
551
+ revertLaunch();
535
552
  throw new Error([
536
553
  `RESULT_PROTOCOL_ERROR: no open work item ${parsed.value.workItemId} exists in this session`,
537
554
  formatResultExcerptForError(resultExcerpt),
@@ -557,6 +574,7 @@ export const WorkflowPlugin = async ({ client, directory }) => {
557
574
  },
558
575
  },
559
576
  });
577
+ revertLaunch();
560
578
  throw new Error([
561
579
  `RESULT_PROTOCOL_ERROR: ${applied.message}`,
562
580
  formatResultExcerptForError(resultExcerpt),
@@ -599,7 +617,8 @@ export const WorkflowPlugin = async ({ client, directory }) => {
599
617
  event: async (input) => {
600
618
  const eventType = input.event.type;
601
619
  const properties = input.event.properties ?? {};
602
- const eventSessionId = properties.sessionID;
620
+ const info = properties.info;
621
+ const eventSessionId = properties.sessionID ?? info?.id;
603
622
  if (!eventSessionId)
604
623
  return;
605
624
  // Hydration happens in tool.execute.before — not here,
@@ -609,7 +628,7 @@ export const WorkflowPlugin = async ({ client, directory }) => {
609
628
  return;
610
629
  }
611
630
  if (eventType === "session.deleted") {
612
- await deleteWorkflowSessionDir(eventSessionId);
631
+ stores.delete(eventSessionId);
613
632
  await deleteWorkflowSessionDir(eventSessionId);
614
633
  await client.app.log({
615
634
  body: {