@selesai/code 0.9.5 → 0.9.8
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/CHANGELOG.md +18 -0
- package/dist/cli/credential-print.js +4 -4
- package/dist/cli.js +0 -0
- package/dist/core/agent-session-runtime.js +1 -4
- package/dist/core/agent-session.d.ts +2 -0
- package/dist/core/agent-session.js +26 -0
- package/dist/core/compaction/branch-summarization.js +30 -30
- package/dist/core/compaction/compaction.js +81 -81
- package/dist/core/compaction/utils.js +2 -2
- package/dist/core/export-html/template.css +1066 -1066
- package/dist/core/export-html/template.html +55 -55
- package/dist/core/export-html/template.js +1864 -1864
- package/dist/core/export-html/vendor/highlight.min.js +1212 -1212
- package/dist/core/export-html/vendor/marked.min.js +78 -78
- package/dist/core/handoff.d.ts +11 -0
- package/dist/core/handoff.js +49 -0
- package/dist/core/messages.js +7 -7
- package/dist/extensions/context-compaction-reminder.test.ts +82 -82
- package/dist/extensions/context-compaction-reminder.ts +28 -28
- package/dist/extensions/handoff-new.ts +14 -75
- package/dist/extensions/pi-intercom/LICENSE +21 -21
- package/dist/extensions/pi-intercom/broker/client.test.ts +83 -83
- package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -387
- package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -114
- package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -153
- package/dist/extensions/pi-intercom/broker/paths.ts +134 -134
- package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -34
- package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -21
- package/dist/extensions/pi-intercom/cwd.test.ts +40 -40
- package/dist/extensions/pi-intercom/cwd.ts +31 -31
- package/dist/extensions/pi-intercom/extension-api.ts +44 -44
- package/dist/extensions/pi-intercom/format-context.test.ts +31 -31
- package/dist/extensions/pi-intercom/format-context.ts +32 -32
- package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -66
- package/dist/extensions/pi-intercom/ui/compose.ts +143 -143
- package/dist/extensions/pi-intercom/ui/session-list.ts +166 -166
- package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +286 -286
- package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +108 -108
- package/dist/extensions/pi-powerline-footer/separators.ts +57 -57
- package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +47 -47
- package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +39 -39
- package/dist/extensions/pi-powerline-footer/theme.example.json +24 -24
- package/dist/extensions/pi-powerline-footer/theme.json +12 -12
- package/dist/extensions/pi-powerline-footer/tps.ts +345 -345
- package/dist/extensions/pi-rewind-hook/README.md +245 -245
- package/dist/extensions/pi-rewind-hook/index.ts +1445 -1445
- package/dist/extensions/pi-rewind-hook/package.json +29 -29
- package/dist/extensions/pi-subagents/install.mjs +0 -0
- package/dist/extensions/pi-web-agent/package.json +31 -31
- package/dist/extensions/pi-web-agent/src/backends/config.ts +205 -205
- package/dist/extensions/pi-web-agent/src/backends/doctor.ts +136 -136
- package/dist/extensions/pi-web-agent/src/backends/factory.ts +152 -152
- package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +25 -25
- package/dist/extensions/pi-web-agent/src/cache/ttl-cache.ts +28 -28
- package/dist/extensions/pi-web-agent/src/changelog-notice.ts +136 -136
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +946 -946
- package/dist/extensions/pi-web-agent/src/extension.ts +126 -126
- package/dist/extensions/pi-web-agent/src/extract/readability.ts +118 -118
- package/dist/extensions/pi-web-agent/src/fetch/browser-resolution.ts +199 -199
- package/dist/extensions/pi-web-agent/src/fetch/firecrawl-fetch.ts +100 -100
- package/dist/extensions/pi-web-agent/src/fetch/headless-fetch.ts +117 -117
- package/dist/extensions/pi-web-agent/src/fetch/http-fetch.ts +67 -67
- package/dist/extensions/pi-web-agent/src/orchestration/answer-synthesizer.ts +60 -60
- package/dist/extensions/pi-web-agent/src/orchestration/candidate-selector.ts +50 -50
- package/dist/extensions/pi-web-agent/src/orchestration/direct-url.ts +52 -52
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-quality.ts +105 -105
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-ranker.ts +45 -45
- package/dist/extensions/pi-web-agent/src/orchestration/index.ts +28 -28
- package/dist/extensions/pi-web-agent/src/orchestration/query-planner.ts +47 -47
- package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +376 -376
- package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +64 -64
- package/dist/extensions/pi-web-agent/src/orchestration/research-worker.ts +181 -181
- package/dist/extensions/pi-web-agent/src/orchestration/source-profile.ts +101 -101
- package/dist/extensions/pi-web-agent/src/orchestration/stop-decider.ts +81 -81
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +210 -210
- package/dist/extensions/pi-web-agent/src/presentation/config.ts +75 -75
- package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +61 -61
- package/dist/extensions/pi-web-agent/src/presentation/fetch-presentation.ts +54 -54
- package/dist/extensions/pi-web-agent/src/presentation/search-presentation.ts +41 -41
- package/dist/extensions/pi-web-agent/src/presentation/select-view.ts +20 -20
- package/dist/extensions/pi-web-agent/src/presentation/types.ts +63 -63
- package/dist/extensions/pi-web-agent/src/search/brave.ts +114 -114
- package/dist/extensions/pi-web-agent/src/search/duckduckgo.ts +72 -72
- package/dist/extensions/pi-web-agent/src/search/searxng.ts +96 -96
- package/dist/extensions/pi-web-agent/src/tools/web-explore.ts +71 -71
- package/dist/extensions/pi-web-agent/src/tools/web-fetch-headless.ts +31 -31
- package/dist/extensions/pi-web-agent/src/tools/web-fetch.ts +31 -31
- package/dist/extensions/pi-web-agent/src/tools/web-search.ts +157 -157
- package/dist/extensions/pi-web-agent/src/types.ts +88 -88
- package/dist/extensions/ponytail/package.json +8 -8
- package/dist/extensions/question/batch.ts +103 -103
- package/dist/extensions/question/constants.ts +30 -30
- package/dist/extensions/question/helpers.ts +58 -58
- package/dist/extensions/question/navigation.ts +14 -14
- package/dist/extensions/question/package.json +19 -19
- package/dist/extensions/question/schemas.ts +43 -43
- package/dist/extensions/question/selection-mode.ts +46 -46
- package/dist/extensions/question/shortcuts.ts +44 -44
- package/dist/extensions/question/types.ts +132 -132
- package/dist/extensions/test-resolve-hook-impl.mjs +6 -6
- package/dist/extensions/test-resolve-hook.mjs +6 -6
- package/dist/extensions/web-agent-onboarding.ts +222 -222
- package/dist/extensions/workflow/package.json +17 -17
- package/dist/modes/interactive/components/model-selector.d.ts +1 -0
- package/dist/modes/interactive/components/model-selector.js +8 -4
- package/dist/modes/interactive/interactive-mode.js +9 -5
- package/dist/modes/rpc/rpc-mode.js +24 -0
- package/dist/modes/rpc/rpc-types.d.ts +19 -0
- package/dist/package-manager-cli.js +71 -71
- package/dist/rpc-entry.js +0 -0
- package/dist/skills/agent-browser/SKILL.md +52 -52
- package/dist/skills/batch-grill-me/SKILL.md +19 -19
- package/dist/skills/grill-me/SKILL.md +10 -10
- package/dist/skills/handoff/SKILL.md +16 -16
- package/dist/skills/handoff-text/SKILL.md +14 -14
- package/dist/skills/implanger/SKILL.md +69 -69
- package/dist/skills/improve-codebase/REFERENCE.md +78 -78
- package/dist/skills/improve-codebase/SKILL.md +178 -178
- package/dist/skills/planger/SKILL.md +166 -166
- package/dist/skills/ponytail/SKILL.md +116 -116
- package/dist/skills/ponytail-audit/SKILL.md +42 -42
- package/dist/skills/ponytail-debt/SKILL.md +45 -45
- package/dist/skills/ponytail-gain/SKILL.md +51 -51
- package/dist/skills/ponytail-help/SKILL.md +70 -70
- package/dist/skills/ponytail-review/SKILL.md +58 -58
- package/dist/skills/selesai-handoff/SKILL.md +20 -20
- package/dist/skills/workflow-creation/SKILL.md +73 -73
- package/dist/themes/powerline-footer/theme.json +33 -33
- package/docs/compaction.md +396 -396
- package/docs/containerization.md +111 -111
- package/docs/development.md +71 -71
- package/docs/docs.json +164 -164
- package/docs/environment-variables.md +86 -86
- package/docs/index.md +83 -83
- package/docs/json.md +82 -82
- package/docs/models.md +502 -502
- package/docs/packages.md +227 -227
- package/docs/plans/subagent-delegation/phase-0-correctness.md +264 -264
- package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +485 -485
- package/docs/plans/subagent-delegation/phase-2-context-controls.md +281 -281
- package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +361 -361
- package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +380 -380
- package/docs/prompt-templates.md +95 -95
- package/docs/providers.md +293 -293
- package/docs/sdk.md +1143 -1143
- package/docs/security.md +59 -59
- package/docs/session-format.md +414 -414
- package/docs/sessions.md +145 -145
- package/docs/shared-host-extensions.md +109 -109
- package/docs/shell-aliases.md +13 -13
- package/docs/skills.md +231 -231
- package/docs/terminal-setup.md +142 -142
- package/docs/termux.md +127 -127
- package/docs/themes.md +295 -295
- package/docs/tmux.md +63 -63
- package/docs/tui.md +927 -927
- package/docs/windows.md +17 -17
- package/examples/README.md +25 -25
- package/examples/extensions/README.md +211 -211
- package/examples/extensions/auto-commit-on-exit.ts +49 -49
- package/examples/extensions/bash-spawn-hook.ts +30 -30
- package/examples/extensions/bookmark.ts +50 -50
- package/examples/extensions/border-status-editor.ts +150 -150
- package/examples/extensions/built-in-tool-renderer.ts +249 -249
- package/examples/extensions/claude-rules.ts +86 -86
- package/examples/extensions/commands.ts +72 -72
- package/examples/extensions/confirm-destructive.ts +59 -59
- package/examples/extensions/custom-compaction.ts +130 -130
- package/examples/extensions/custom-footer.ts +64 -64
- package/examples/extensions/custom-header.ts +73 -73
- package/examples/extensions/custom-provider-anthropic/index.ts +610 -610
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -24
- package/examples/extensions/custom-provider-anthropic/package.json +19 -19
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +404 -404
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -16
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -82
- package/examples/extensions/dirty-repo-guard.ts +56 -56
- package/examples/extensions/doom-overlay/README.md +46 -46
- package/examples/extensions/doom-overlay/doom/build/doom.js +21 -21
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +152 -152
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -72
- package/examples/extensions/doom-overlay/doom-component.ts +132 -132
- package/examples/extensions/doom-overlay/doom-engine.ts +173 -173
- package/examples/extensions/doom-overlay/doom-keys.ts +104 -104
- package/examples/extensions/doom-overlay/index.ts +74 -74
- package/examples/extensions/doom-overlay/wad-finder.ts +51 -51
- package/examples/extensions/dynamic-resources/SKILL.md +8 -8
- package/examples/extensions/dynamic-resources/dynamic.json +79 -79
- package/examples/extensions/dynamic-resources/dynamic.md +5 -5
- package/examples/extensions/dynamic-resources/index.ts +15 -15
- package/examples/extensions/dynamic-tools.ts +74 -74
- package/examples/extensions/event-bus.ts +43 -43
- package/examples/extensions/file-trigger.ts +41 -41
- package/examples/extensions/git-checkpoint.ts +53 -53
- package/examples/extensions/git-merge-and-resolve.ts +115 -115
- package/examples/extensions/github-issue-autocomplete.ts +185 -185
- package/examples/extensions/gondolin/index.ts +531 -531
- package/examples/extensions/gondolin/package-lock.json +185 -185
- package/examples/extensions/gondolin/package.json +19 -19
- package/examples/extensions/handoff.ts +199 -199
- package/examples/extensions/hello.ts +26 -26
- package/examples/extensions/hidden-thinking-label.ts +53 -53
- package/examples/extensions/inline-bash.ts +94 -94
- package/examples/extensions/input-transform-streaming.ts +39 -39
- package/examples/extensions/input-transform.ts +43 -43
- package/examples/extensions/interactive-shell.ts +196 -196
- package/examples/extensions/mac-system-theme.ts +47 -47
- package/examples/extensions/message-renderer.ts +59 -59
- package/examples/extensions/minimal-mode.ts +426 -426
- package/examples/extensions/modal-editor.ts +85 -85
- package/examples/extensions/model-status.ts +31 -31
- package/examples/extensions/notify.ts +55 -55
- package/examples/extensions/overlay-qa-tests.ts +1450 -1450
- package/examples/extensions/overlay-test.ts +153 -153
- package/examples/extensions/permission-gate.ts +34 -34
- package/examples/extensions/pirate.ts +47 -47
- package/examples/extensions/plan-mode/README.md +66 -66
- package/examples/extensions/plan-mode/index.ts +390 -390
- package/examples/extensions/plan-mode/utils.ts +168 -168
- package/examples/extensions/preset.ts +436 -436
- package/examples/extensions/project-trust.ts +64 -64
- package/examples/extensions/prompt-customizer.ts +97 -97
- package/examples/extensions/protected-paths.ts +30 -30
- package/examples/extensions/provider-payload.ts +18 -18
- package/examples/extensions/qna.ts +122 -122
- package/examples/extensions/question.ts +285 -285
- package/examples/extensions/questionnaire.ts +448 -448
- package/examples/extensions/rainbow-editor.ts +88 -88
- package/examples/extensions/reload-runtime.ts +37 -37
- package/examples/extensions/rpc-demo.ts +118 -118
- package/examples/extensions/sandbox/index.ts +321 -321
- package/examples/extensions/sandbox/package-lock.json +92 -92
- package/examples/extensions/sandbox/package.json +19 -19
- package/examples/extensions/send-user-message.ts +97 -97
- package/examples/extensions/session-name.ts +27 -27
- package/examples/extensions/shutdown-command.ts +63 -63
- package/examples/extensions/snake.ts +343 -343
- package/examples/extensions/space-invaders.ts +560 -560
- package/examples/extensions/ssh.ts +220 -220
- package/examples/extensions/status-line.ts +32 -32
- package/examples/extensions/structured-output.ts +65 -65
- package/examples/extensions/subagent/README.md +175 -175
- package/examples/extensions/subagent/agents/planner.md +37 -37
- package/examples/extensions/subagent/agents/reviewer.md +35 -35
- package/examples/extensions/subagent/agents/scout.md +50 -50
- package/examples/extensions/subagent/agents/worker.md +24 -24
- package/examples/extensions/subagent/agents.ts +126 -126
- package/examples/extensions/subagent/index.ts +1015 -1015
- package/examples/extensions/subagent/prompts/implement-and-review.md +10 -10
- package/examples/extensions/subagent/prompts/implement.md +10 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -9
- package/examples/extensions/summarize.ts +209 -209
- package/examples/extensions/system-prompt-header.ts +17 -17
- package/examples/extensions/tic-tac-toe.ts +1008 -1008
- package/examples/extensions/timed-confirm.ts +70 -70
- package/examples/extensions/titlebar-spinner.ts +58 -58
- package/examples/extensions/todo.ts +297 -297
- package/examples/extensions/tool-override.ts +144 -144
- package/examples/extensions/tools.ts +146 -146
- package/examples/extensions/trigger-compact.ts +50 -50
- package/examples/extensions/truncated-tool.ts +195 -195
- package/examples/extensions/widget-placement.ts +9 -9
- package/examples/extensions/with-deps/index.ts +32 -32
- package/examples/extensions/with-deps/package-lock.json +31 -31
- package/examples/extensions/with-deps/package.json +22 -22
- package/examples/extensions/working-indicator.ts +123 -123
- package/examples/extensions/working-message-test.ts +25 -25
- package/examples/rpc-extension-ui.ts +632 -632
- package/examples/sdk/01-minimal.ts +26 -26
- package/examples/sdk/02-custom-model.ts +53 -53
- package/examples/sdk/03-custom-prompt.ts +75 -75
- package/examples/sdk/04-skills.ts +55 -55
- package/examples/sdk/05-tools.ts +48 -48
- package/examples/sdk/06-extensions.ts +99 -99
- package/examples/sdk/07-context-files.ts +47 -47
- package/examples/sdk/08-prompt-templates.ts +51 -51
- package/examples/sdk/09-api-keys-and-oauth.ts +52 -52
- package/examples/sdk/10-settings.ts +53 -53
- package/examples/sdk/11-sessions.ts +52 -52
- package/examples/sdk/12-full-control.ts +79 -79
- package/examples/sdk/13-session-runtime.ts +67 -67
- package/examples/sdk/README.md +144 -144
- package/package.json +1 -1
|
@@ -1,210 +1,210 @@
|
|
|
1
|
-
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { CONFIG_DIR_NAME, getAgentDir } from '@selesai/code';
|
|
4
|
-
import {
|
|
5
|
-
DEFAULT_BACKEND_CONFIG,
|
|
6
|
-
extractBackendConfigOverride,
|
|
7
|
-
mergeBackendConfigLayers,
|
|
8
|
-
type BackendConfig,
|
|
9
|
-
type BackendConfigFile,
|
|
10
|
-
type BackendConfigOverride
|
|
11
|
-
} from '../backends/config.js';
|
|
12
|
-
import {
|
|
13
|
-
DEFAULT_PRESENTATION_CONFIG,
|
|
14
|
-
extractPresentationConfigOverride,
|
|
15
|
-
mergePresentationConfigLayers
|
|
16
|
-
} from './config.js';
|
|
17
|
-
import type {
|
|
18
|
-
PresentationConfig,
|
|
19
|
-
PresentationConfigFile,
|
|
20
|
-
PresentationConfigOverride,
|
|
21
|
-
PresentationScope
|
|
22
|
-
} from './types.js';
|
|
23
|
-
|
|
24
|
-
export type PresentationConfigStoreOptions = {
|
|
25
|
-
homeDir?: string;
|
|
26
|
-
projectDir?: string;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type PresentationConfigLayer = {
|
|
30
|
-
path: string;
|
|
31
|
-
exists: boolean;
|
|
32
|
-
rawConfig?: PresentationConfigOverride;
|
|
33
|
-
rawBackends?: BackendConfigOverride;
|
|
34
|
-
error?: string;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export type LoadedPresentationConfig = {
|
|
38
|
-
global: PresentationConfigLayer;
|
|
39
|
-
project: PresentationConfigLayer;
|
|
40
|
-
effectiveConfig: PresentationConfig;
|
|
41
|
-
effectiveBackends: BackendConfig;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export function getPresentationConfigPaths(options: PresentationConfigStoreOptions = {}) {
|
|
45
|
-
// ponytail: use selesai's getAgentDir() so the config dir respects the active
|
|
46
|
-
// CONFIG_DIR_NAME (e.g. .selesai) instead of upstream's hardcoded .pi. Ceiling:
|
|
47
|
-
// if selesai changes the extensions config layout, update here; upgrade path =
|
|
48
|
-
// upstream pi-web-agent accepting a config-dir resolver.
|
|
49
|
-
const projectDir = options.projectDir ?? process.cwd();
|
|
50
|
-
const agentDir = getAgentDir();
|
|
51
|
-
const globalConfigRoot = path.join(agentDir, 'extensions', 'pi-web-agent', 'config.json');
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
globalPath: globalConfigRoot,
|
|
55
|
-
// ponytail: project config dir is CONFIG_DIR_NAME (e.g. .selesai), not the
|
|
56
|
-
// upstream hardcoded .pi. Ceiling: assumes the project-local extension
|
|
57
|
-
// config layout matches the global one; upgrade path = upstream accepting a
|
|
58
|
-
// config-dir resolver for project paths too.
|
|
59
|
-
projectPath: path.join(projectDir, CONFIG_DIR_NAME, 'extensions', 'pi-web-agent', 'config.json')
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
type AgentConfigFile = PresentationConfigFile & BackendConfigFile;
|
|
64
|
-
|
|
65
|
-
type LegacyPresentationConfigFile = NonNullable<PresentationConfigFile['presentation']>;
|
|
66
|
-
|
|
67
|
-
function hasPresentationRoot(parsed: AgentConfigFile) {
|
|
68
|
-
return parsed.presentation !== undefined;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function readPresentationConfigFile(filePath: string): Promise<PresentationConfigLayer> {
|
|
72
|
-
try {
|
|
73
|
-
const rawText = await readFile(filePath, 'utf8');
|
|
74
|
-
const parsed = JSON.parse(rawText) as AgentConfigFile;
|
|
75
|
-
const presentationFile: PresentationConfigFile = hasPresentationRoot(parsed)
|
|
76
|
-
? parsed
|
|
77
|
-
: { presentation: parsed as LegacyPresentationConfigFile };
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
path: filePath,
|
|
81
|
-
exists: true,
|
|
82
|
-
rawConfig: extractPresentationConfigOverride(presentationFile),
|
|
83
|
-
rawBackends: extractBackendConfigOverride(parsed)
|
|
84
|
-
};
|
|
85
|
-
} catch (error) {
|
|
86
|
-
if ((error as NodeJS.ErrnoException | undefined)?.code === 'ENOENT') {
|
|
87
|
-
return { path: filePath, exists: false };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
-
return {
|
|
92
|
-
path: filePath,
|
|
93
|
-
exists: true,
|
|
94
|
-
error: message
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function serializePresentationConfigOverride(config: PresentationConfigOverride): PresentationConfigFile {
|
|
100
|
-
const presentation: NonNullable<PresentationConfigFile['presentation']> = {};
|
|
101
|
-
|
|
102
|
-
if (config.defaultMode) {
|
|
103
|
-
presentation.defaultMode = config.defaultMode;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (Object.keys(config.tools).length > 0) {
|
|
107
|
-
presentation.tools = config.tools;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return { presentation };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function serializeBackendConfigOverride(config: BackendConfigOverride): BackendConfigFile {
|
|
114
|
-
const backends: NonNullable<BackendConfigFile['backends']> = {};
|
|
115
|
-
|
|
116
|
-
if (config.search && Object.keys(config.search).length > 0) {
|
|
117
|
-
backends.search = { ...config.search };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (config.fetch && Object.keys(config.fetch).length > 0) {
|
|
121
|
-
const { apiKey: _apiKey, ...fetch } = config.fetch;
|
|
122
|
-
backends.fetch = { ...fetch };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (config.headless && Object.keys(config.headless).length > 0) {
|
|
126
|
-
backends.headless = { ...config.headless };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return { backends };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async function readConfigFileForWrite(filePath: string): Promise<AgentConfigFile> {
|
|
133
|
-
try {
|
|
134
|
-
return JSON.parse(await readFile(filePath, 'utf8')) as AgentConfigFile;
|
|
135
|
-
} catch (error) {
|
|
136
|
-
if ((error as NodeJS.ErrnoException | undefined)?.code === 'ENOENT') {
|
|
137
|
-
return {};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function writeConfigFile(filePath: string, config: AgentConfigFile) {
|
|
145
|
-
await mkdir(path.dirname(filePath), { recursive: true });
|
|
146
|
-
await writeFile(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export async function loadPresentationConfigLayers(
|
|
150
|
-
options: PresentationConfigStoreOptions = {}
|
|
151
|
-
): Promise<LoadedPresentationConfig> {
|
|
152
|
-
const { globalPath, projectPath } = getPresentationConfigPaths(options);
|
|
153
|
-
const global = await readPresentationConfigFile(globalPath);
|
|
154
|
-
const project = await readPresentationConfigFile(projectPath);
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
global,
|
|
158
|
-
project,
|
|
159
|
-
effectiveConfig: mergePresentationConfigLayers(
|
|
160
|
-
DEFAULT_PRESENTATION_CONFIG,
|
|
161
|
-
global.rawConfig,
|
|
162
|
-
project.rawConfig
|
|
163
|
-
),
|
|
164
|
-
effectiveBackends: mergeBackendConfigLayers(
|
|
165
|
-
DEFAULT_BACKEND_CONFIG,
|
|
166
|
-
global.rawBackends,
|
|
167
|
-
project.rawBackends
|
|
168
|
-
)
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export async function savePresentationConfigScope(
|
|
173
|
-
options: PresentationConfigStoreOptions,
|
|
174
|
-
scope: PresentationScope,
|
|
175
|
-
config: PresentationConfigOverride
|
|
176
|
-
) {
|
|
177
|
-
const { globalPath, projectPath } = getPresentationConfigPaths(options);
|
|
178
|
-
const filePath = scope === 'global' ? globalPath : projectPath;
|
|
179
|
-
const existing = await readConfigFileForWrite(filePath);
|
|
180
|
-
|
|
181
|
-
await writeConfigFile(filePath, {
|
|
182
|
-
...existing,
|
|
183
|
-
...serializePresentationConfigOverride(config)
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export async function saveBackendConfigScope(
|
|
188
|
-
options: PresentationConfigStoreOptions,
|
|
189
|
-
scope: PresentationScope,
|
|
190
|
-
config: BackendConfigOverride
|
|
191
|
-
) {
|
|
192
|
-
const { globalPath, projectPath } = getPresentationConfigPaths(options);
|
|
193
|
-
const filePath = scope === 'global' ? globalPath : projectPath;
|
|
194
|
-
const existing = await readConfigFileForWrite(filePath);
|
|
195
|
-
|
|
196
|
-
await writeConfigFile(filePath, {
|
|
197
|
-
...existing,
|
|
198
|
-
...serializeBackendConfigOverride(config)
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export async function resetPresentationConfigScope(
|
|
203
|
-
options: PresentationConfigStoreOptions,
|
|
204
|
-
scope: PresentationScope
|
|
205
|
-
) {
|
|
206
|
-
const { globalPath, projectPath } = getPresentationConfigPaths(options);
|
|
207
|
-
const filePath = scope === 'global' ? globalPath : projectPath;
|
|
208
|
-
|
|
209
|
-
await rm(filePath, { force: true });
|
|
210
|
-
}
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { CONFIG_DIR_NAME, getAgentDir } from '@selesai/code';
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_BACKEND_CONFIG,
|
|
6
|
+
extractBackendConfigOverride,
|
|
7
|
+
mergeBackendConfigLayers,
|
|
8
|
+
type BackendConfig,
|
|
9
|
+
type BackendConfigFile,
|
|
10
|
+
type BackendConfigOverride
|
|
11
|
+
} from '../backends/config.js';
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_PRESENTATION_CONFIG,
|
|
14
|
+
extractPresentationConfigOverride,
|
|
15
|
+
mergePresentationConfigLayers
|
|
16
|
+
} from './config.js';
|
|
17
|
+
import type {
|
|
18
|
+
PresentationConfig,
|
|
19
|
+
PresentationConfigFile,
|
|
20
|
+
PresentationConfigOverride,
|
|
21
|
+
PresentationScope
|
|
22
|
+
} from './types.js';
|
|
23
|
+
|
|
24
|
+
export type PresentationConfigStoreOptions = {
|
|
25
|
+
homeDir?: string;
|
|
26
|
+
projectDir?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type PresentationConfigLayer = {
|
|
30
|
+
path: string;
|
|
31
|
+
exists: boolean;
|
|
32
|
+
rawConfig?: PresentationConfigOverride;
|
|
33
|
+
rawBackends?: BackendConfigOverride;
|
|
34
|
+
error?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type LoadedPresentationConfig = {
|
|
38
|
+
global: PresentationConfigLayer;
|
|
39
|
+
project: PresentationConfigLayer;
|
|
40
|
+
effectiveConfig: PresentationConfig;
|
|
41
|
+
effectiveBackends: BackendConfig;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function getPresentationConfigPaths(options: PresentationConfigStoreOptions = {}) {
|
|
45
|
+
// ponytail: use selesai's getAgentDir() so the config dir respects the active
|
|
46
|
+
// CONFIG_DIR_NAME (e.g. .selesai) instead of upstream's hardcoded .pi. Ceiling:
|
|
47
|
+
// if selesai changes the extensions config layout, update here; upgrade path =
|
|
48
|
+
// upstream pi-web-agent accepting a config-dir resolver.
|
|
49
|
+
const projectDir = options.projectDir ?? process.cwd();
|
|
50
|
+
const agentDir = getAgentDir();
|
|
51
|
+
const globalConfigRoot = path.join(agentDir, 'extensions', 'pi-web-agent', 'config.json');
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
globalPath: globalConfigRoot,
|
|
55
|
+
// ponytail: project config dir is CONFIG_DIR_NAME (e.g. .selesai), not the
|
|
56
|
+
// upstream hardcoded .pi. Ceiling: assumes the project-local extension
|
|
57
|
+
// config layout matches the global one; upgrade path = upstream accepting a
|
|
58
|
+
// config-dir resolver for project paths too.
|
|
59
|
+
projectPath: path.join(projectDir, CONFIG_DIR_NAME, 'extensions', 'pi-web-agent', 'config.json')
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type AgentConfigFile = PresentationConfigFile & BackendConfigFile;
|
|
64
|
+
|
|
65
|
+
type LegacyPresentationConfigFile = NonNullable<PresentationConfigFile['presentation']>;
|
|
66
|
+
|
|
67
|
+
function hasPresentationRoot(parsed: AgentConfigFile) {
|
|
68
|
+
return parsed.presentation !== undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function readPresentationConfigFile(filePath: string): Promise<PresentationConfigLayer> {
|
|
72
|
+
try {
|
|
73
|
+
const rawText = await readFile(filePath, 'utf8');
|
|
74
|
+
const parsed = JSON.parse(rawText) as AgentConfigFile;
|
|
75
|
+
const presentationFile: PresentationConfigFile = hasPresentationRoot(parsed)
|
|
76
|
+
? parsed
|
|
77
|
+
: { presentation: parsed as LegacyPresentationConfigFile };
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
path: filePath,
|
|
81
|
+
exists: true,
|
|
82
|
+
rawConfig: extractPresentationConfigOverride(presentationFile),
|
|
83
|
+
rawBackends: extractBackendConfigOverride(parsed)
|
|
84
|
+
};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if ((error as NodeJS.ErrnoException | undefined)?.code === 'ENOENT') {
|
|
87
|
+
return { path: filePath, exists: false };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
+
return {
|
|
92
|
+
path: filePath,
|
|
93
|
+
exists: true,
|
|
94
|
+
error: message
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function serializePresentationConfigOverride(config: PresentationConfigOverride): PresentationConfigFile {
|
|
100
|
+
const presentation: NonNullable<PresentationConfigFile['presentation']> = {};
|
|
101
|
+
|
|
102
|
+
if (config.defaultMode) {
|
|
103
|
+
presentation.defaultMode = config.defaultMode;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (Object.keys(config.tools).length > 0) {
|
|
107
|
+
presentation.tools = config.tools;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { presentation };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function serializeBackendConfigOverride(config: BackendConfigOverride): BackendConfigFile {
|
|
114
|
+
const backends: NonNullable<BackendConfigFile['backends']> = {};
|
|
115
|
+
|
|
116
|
+
if (config.search && Object.keys(config.search).length > 0) {
|
|
117
|
+
backends.search = { ...config.search };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (config.fetch && Object.keys(config.fetch).length > 0) {
|
|
121
|
+
const { apiKey: _apiKey, ...fetch } = config.fetch;
|
|
122
|
+
backends.fetch = { ...fetch };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (config.headless && Object.keys(config.headless).length > 0) {
|
|
126
|
+
backends.headless = { ...config.headless };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { backends };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function readConfigFileForWrite(filePath: string): Promise<AgentConfigFile> {
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(await readFile(filePath, 'utf8')) as AgentConfigFile;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if ((error as NodeJS.ErrnoException | undefined)?.code === 'ENOENT') {
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function writeConfigFile(filePath: string, config: AgentConfigFile) {
|
|
145
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
146
|
+
await writeFile(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function loadPresentationConfigLayers(
|
|
150
|
+
options: PresentationConfigStoreOptions = {}
|
|
151
|
+
): Promise<LoadedPresentationConfig> {
|
|
152
|
+
const { globalPath, projectPath } = getPresentationConfigPaths(options);
|
|
153
|
+
const global = await readPresentationConfigFile(globalPath);
|
|
154
|
+
const project = await readPresentationConfigFile(projectPath);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
global,
|
|
158
|
+
project,
|
|
159
|
+
effectiveConfig: mergePresentationConfigLayers(
|
|
160
|
+
DEFAULT_PRESENTATION_CONFIG,
|
|
161
|
+
global.rawConfig,
|
|
162
|
+
project.rawConfig
|
|
163
|
+
),
|
|
164
|
+
effectiveBackends: mergeBackendConfigLayers(
|
|
165
|
+
DEFAULT_BACKEND_CONFIG,
|
|
166
|
+
global.rawBackends,
|
|
167
|
+
project.rawBackends
|
|
168
|
+
)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function savePresentationConfigScope(
|
|
173
|
+
options: PresentationConfigStoreOptions,
|
|
174
|
+
scope: PresentationScope,
|
|
175
|
+
config: PresentationConfigOverride
|
|
176
|
+
) {
|
|
177
|
+
const { globalPath, projectPath } = getPresentationConfigPaths(options);
|
|
178
|
+
const filePath = scope === 'global' ? globalPath : projectPath;
|
|
179
|
+
const existing = await readConfigFileForWrite(filePath);
|
|
180
|
+
|
|
181
|
+
await writeConfigFile(filePath, {
|
|
182
|
+
...existing,
|
|
183
|
+
...serializePresentationConfigOverride(config)
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function saveBackendConfigScope(
|
|
188
|
+
options: PresentationConfigStoreOptions,
|
|
189
|
+
scope: PresentationScope,
|
|
190
|
+
config: BackendConfigOverride
|
|
191
|
+
) {
|
|
192
|
+
const { globalPath, projectPath } = getPresentationConfigPaths(options);
|
|
193
|
+
const filePath = scope === 'global' ? globalPath : projectPath;
|
|
194
|
+
const existing = await readConfigFileForWrite(filePath);
|
|
195
|
+
|
|
196
|
+
await writeConfigFile(filePath, {
|
|
197
|
+
...existing,
|
|
198
|
+
...serializeBackendConfigOverride(config)
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function resetPresentationConfigScope(
|
|
203
|
+
options: PresentationConfigStoreOptions,
|
|
204
|
+
scope: PresentationScope
|
|
205
|
+
) {
|
|
206
|
+
const { globalPath, projectPath } = getPresentationConfigPaths(options);
|
|
207
|
+
const filePath = scope === 'global' ? globalPath : projectPath;
|
|
208
|
+
|
|
209
|
+
await rm(filePath, { force: true });
|
|
210
|
+
}
|
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
PRESENTATION_MODES,
|
|
3
|
-
type PresentationConfig,
|
|
4
|
-
type PresentationConfigFile,
|
|
5
|
-
type PresentationConfigOverride,
|
|
6
|
-
type PresentationMode,
|
|
7
|
-
type PresentationToolName
|
|
8
|
-
} from './types.js';
|
|
9
|
-
|
|
10
|
-
const PRESENTATION_MODE_SET = new Set<string>(PRESENTATION_MODES);
|
|
11
|
-
|
|
12
|
-
export const DEFAULT_PRESENTATION_CONFIG: PresentationConfig = {
|
|
13
|
-
defaultMode: 'compact',
|
|
14
|
-
tools: {}
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export function isPresentationMode(value: unknown): value is PresentationMode {
|
|
18
|
-
return typeof value === 'string' && PRESENTATION_MODE_SET.has(value);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function extractPresentationConfigOverride(
|
|
22
|
-
file: PresentationConfigFile | null | undefined
|
|
23
|
-
): PresentationConfigOverride {
|
|
24
|
-
const presentation = file?.presentation;
|
|
25
|
-
const tools = Object.fromEntries(
|
|
26
|
-
Object.entries(presentation?.tools ?? {}).flatMap(([toolName, value]) => {
|
|
27
|
-
if (!value || !isPresentationMode(value.mode)) {
|
|
28
|
-
return [];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return [[toolName, { mode: value.mode }]];
|
|
32
|
-
})
|
|
33
|
-
) as PresentationConfig['tools'];
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
defaultMode: isPresentationMode(presentation?.defaultMode)
|
|
37
|
-
? presentation.defaultMode
|
|
38
|
-
: undefined,
|
|
39
|
-
tools
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function normalizePresentationConfigFile(
|
|
44
|
-
file: PresentationConfigFile | null | undefined
|
|
45
|
-
): PresentationConfig {
|
|
46
|
-
const override = extractPresentationConfigOverride(file);
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
defaultMode: override.defaultMode ?? DEFAULT_PRESENTATION_CONFIG.defaultMode,
|
|
50
|
-
tools: override.tools
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function mergePresentationConfigLayers(
|
|
55
|
-
defaults: PresentationConfig,
|
|
56
|
-
globalConfig?: PresentationConfigOverride,
|
|
57
|
-
projectConfig?: PresentationConfigOverride
|
|
58
|
-
): PresentationConfig {
|
|
59
|
-
return {
|
|
60
|
-
defaultMode:
|
|
61
|
-
projectConfig?.defaultMode ?? globalConfig?.defaultMode ?? defaults.defaultMode,
|
|
62
|
-
tools: {
|
|
63
|
-
...defaults.tools,
|
|
64
|
-
...globalConfig?.tools,
|
|
65
|
-
...projectConfig?.tools
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function resolvePresentationMode(
|
|
71
|
-
toolName: PresentationToolName,
|
|
72
|
-
config: PresentationConfig = DEFAULT_PRESENTATION_CONFIG
|
|
73
|
-
): PresentationMode {
|
|
74
|
-
return config.tools[toolName]?.mode ?? config.defaultMode;
|
|
75
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
PRESENTATION_MODES,
|
|
3
|
+
type PresentationConfig,
|
|
4
|
+
type PresentationConfigFile,
|
|
5
|
+
type PresentationConfigOverride,
|
|
6
|
+
type PresentationMode,
|
|
7
|
+
type PresentationToolName
|
|
8
|
+
} from './types.js';
|
|
9
|
+
|
|
10
|
+
const PRESENTATION_MODE_SET = new Set<string>(PRESENTATION_MODES);
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_PRESENTATION_CONFIG: PresentationConfig = {
|
|
13
|
+
defaultMode: 'compact',
|
|
14
|
+
tools: {}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function isPresentationMode(value: unknown): value is PresentationMode {
|
|
18
|
+
return typeof value === 'string' && PRESENTATION_MODE_SET.has(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function extractPresentationConfigOverride(
|
|
22
|
+
file: PresentationConfigFile | null | undefined
|
|
23
|
+
): PresentationConfigOverride {
|
|
24
|
+
const presentation = file?.presentation;
|
|
25
|
+
const tools = Object.fromEntries(
|
|
26
|
+
Object.entries(presentation?.tools ?? {}).flatMap(([toolName, value]) => {
|
|
27
|
+
if (!value || !isPresentationMode(value.mode)) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [[toolName, { mode: value.mode }]];
|
|
32
|
+
})
|
|
33
|
+
) as PresentationConfig['tools'];
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
defaultMode: isPresentationMode(presentation?.defaultMode)
|
|
37
|
+
? presentation.defaultMode
|
|
38
|
+
: undefined,
|
|
39
|
+
tools
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function normalizePresentationConfigFile(
|
|
44
|
+
file: PresentationConfigFile | null | undefined
|
|
45
|
+
): PresentationConfig {
|
|
46
|
+
const override = extractPresentationConfigOverride(file);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
defaultMode: override.defaultMode ?? DEFAULT_PRESENTATION_CONFIG.defaultMode,
|
|
50
|
+
tools: override.tools
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function mergePresentationConfigLayers(
|
|
55
|
+
defaults: PresentationConfig,
|
|
56
|
+
globalConfig?: PresentationConfigOverride,
|
|
57
|
+
projectConfig?: PresentationConfigOverride
|
|
58
|
+
): PresentationConfig {
|
|
59
|
+
return {
|
|
60
|
+
defaultMode:
|
|
61
|
+
projectConfig?.defaultMode ?? globalConfig?.defaultMode ?? defaults.defaultMode,
|
|
62
|
+
tools: {
|
|
63
|
+
...defaults.tools,
|
|
64
|
+
...globalConfig?.tools,
|
|
65
|
+
...projectConfig?.tools
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolvePresentationMode(
|
|
71
|
+
toolName: PresentationToolName,
|
|
72
|
+
config: PresentationConfig = DEFAULT_PRESENTATION_CONFIG
|
|
73
|
+
): PresentationMode {
|
|
74
|
+
return config.tools[toolName]?.mode ?? config.defaultMode;
|
|
75
|
+
}
|