@akira-tl/forgerelay 0.6.0 → 0.6.2
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 +36 -0
- package/README.md +47 -12
- package/dist/activity/mcp-query-tools.js +48 -2
- package/dist/activity/query-service.js +1 -0
- package/dist/cli.js +181 -4
- package/dist/composite-activity.js +155 -0
- package/dist/composite-workspaces.js +197 -0
- package/dist/config.js +4 -1
- package/dist/oauth/router.js +21 -1
- package/dist/oauth-provider.js +32 -4
- package/dist/oauth-store.js +9 -0
- package/dist/remote-auth.js +110 -0
- package/dist/remote-transport.js +196 -0
- package/dist/remote-workspace-relay.js +473 -0
- package/dist/server.js +824 -121
- package/dist/ui/.vite/manifest.json +33 -33
- package/dist/ui/activity-panel-app.html +3 -3
- package/dist/ui/assets/{activity-panel-app-CjZVvVNc.js → activity-panel-app-E1ju2dqI.js} +1 -1
- package/dist/ui/assets/{heavy-payload-vGgBRvNX.js → heavy-payload-CeW-n9w5.js} +1 -1
- package/dist/ui/assets/{review-payload-4erWKckt.js → review-payload-B9CO298v.js} +1 -1
- package/dist/ui/assets/{scrollbar-CaOPzUJd.js → scrollbar-C2twAENW.js} +1 -1
- package/dist/ui/assets/workspace-app-BztEvZIC.js +5 -0
- package/dist/ui/assets/{workspace-app-DkAiSl_0.js → workspace-app-CwbJnb_w.js} +1 -1
- package/dist/ui/assets/workspace-app-YnUST8IP.css +1 -0
- package/dist/ui/assets/workspace-app-rKuhdae8.js +1 -0
- package/dist/ui/assets/workspace-lifecycle-app-CEfMdudP.js +1 -0
- package/dist/ui/workspace-app.html +4 -4
- package/dist/ui/workspace-lifecycle-app.html +4 -4
- package/dist/user-config.js +131 -5
- package/docs/configuration.md +26 -3
- package/docs/debugging.md +7 -0
- package/docs/versioning.md +11 -19
- package/package.json +5 -2
- package/scripts/ci/verify.mjs +38 -0
- package/scripts/debug/runtime.mjs +23 -1
- package/scripts/debug/runtime.test.mjs +14 -2
- package/scripts/debug/serve.mjs +4 -4
- package/scripts/release/pack.mjs +36 -0
- package/scripts/release/publish.mjs +157 -0
- package/scripts/release/release-gate.test.mjs +73 -16
- package/scripts/release-parity.mjs +5 -13
- package/scripts/release-proof.mjs +28 -19
- package/scripts/release-proof.test.mjs +32 -11
- package/scripts/release-version.mjs +1 -1
- package/dist/ui/assets/workspace-app-CcrHAUIn.css +0 -1
- package/dist/ui/assets/workspace-app-DJmkPYJC.js +0 -1
- package/dist/ui/assets/workspace-app-QyauBrJX.js +0 -5
- package/dist/ui/assets/workspace-lifecycle-app-BIXEo53I.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.forgerelay-panel{border:1px solid var(--tool-card-border);background:var(--tool-card-header-bg);width:100%;color:var(--color-text-primary,#f5f5f6);border-radius:12px;overflow:hidden}.workspace-panel{background:var(--tool-card-header-bg);width:100%}.workspace-panel-header{grid-template-columns:22px minmax(0,1fr) auto;align-items:center;gap:10px;min-height:58px;padding:9px 12px;display:grid}.workspace-panel-icon{background:color-mix(in srgb, var(--tool-accent) 9%, transparent);width:22px;height:22px;color:color-mix(in srgb, var(--tool-accent) 72%, var(--color-text-tertiary,#a3a3aa));border-radius:6px;place-items:center;display:grid}.workspace-panel-icon-svg{stroke-width:1.8px;width:14px;height:14px}.workspace-panel-title-group{gap:2px;min-width:0;display:grid}.workspace-panel-title{font-size:var(--font-text-sm-size,14px);font-weight:600;line-height:1.3}.workspace-panel-subtitle,.workspace-panel-mode{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,11px);line-height:1.35}.workspace-panel-subtitle{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.workspace-panel-mode{border:1px solid color-mix(in srgb, var(--tool-card-divider) 76%, transparent);white-space:nowrap;border-radius:999px;padding:2px 7px}.workspace-panel .workspace-details{border-top:1px solid var(--tool-card-divider);background:var(--tool-card-body-bg);max-height:none}.workspace-panel-pending-dot{background:var(--color-text-info,#38bdf8);width:8px;height:8px;box-shadow:0 0 0 3px color-mix(in srgb, var(--color-text-info,#38bdf8) 12%, transparent);border-radius:999px;justify-self:center}.activity-panel{border:1px solid var(--tool-card-border);background:var(--tool-card-header-bg);width:100%;color:var(--color-text-primary,#f5f5f6);border-radius:12px;overflow:hidden}.forgerelay-panel .activity-panel{border:0;border-top:1px solid var(--tool-card-divider);border-radius:0}.activity-panel-header{width:100%;min-height:58px;color:inherit;cursor:pointer;font:inherit;text-align:left;background:0 0;border:0;grid-template-columns:14px minmax(0,1fr) auto 20px;align-items:center;gap:10px;padding:9px 12px;display:grid}.activity-panel-header:hover{background:var(--tool-card-hover-bg)}.activity-panel-header:focus-visible{outline:2px solid color-mix(in srgb, var(--color-text-info,#38bdf8) 72%, transparent);outline-offset:-2px}.activity-panel-header-pending{cursor:default}.activity-panel-header-pending:hover{background:0 0}.activity-panel-pending-spacer{width:20px}.activity-panel-status{background:var(--color-text-info,#38bdf8);width:8px;height:8px;box-shadow:0 0 0 3px color-mix(in srgb, var(--color-text-info,#38bdf8) 12%, transparent);border-radius:9999px;justify-self:center}.activity-panel-status.state-done{background:var(--color-success-text,#6fda83);box-shadow:0 0 0 3px color-mix(in srgb, var(--color-success-text,#6fda83) 12%, transparent)}.activity-panel-status.state-error{background:var(--color-danger-text,#ee7676);box-shadow:0 0 0 3px color-mix(in srgb, var(--color-danger-text,#ee7676) 12%, transparent)}.activity-panel-title-group{gap:2px;min-width:0;display:grid}.activity-panel-title{font-size:var(--font-text-sm-size,14px);font-weight:600;line-height:1.3}.activity-panel-subtitle,.activity-panel-count{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,11px);line-height:1.35}.activity-panel-count{white-space:nowrap}.activity-panel-body{border-top:1px solid var(--tool-card-divider);background:var(--tool-card-body-bg);display:grid}.activity-viewport{overscroll-behavior:contain;max-height:420px;overflow:hidden auto}.activity-list{display:grid}.activity-group+.activity-group{border-top:1px solid var(--tool-card-divider)}.activity-group.grouped>.activity-row.parent{background:color-mix(in srgb, var(--color-background-secondary,#272727) 54%, transparent)}.activity-children{border-top:1px solid color-mix(in srgb, var(--tool-card-divider) 72%, transparent);display:grid}.activity-row{--activity-accent:var(--color-text-secondary,#b6b6bd);--activity-phase:var(--color-text-tertiary,#a3a3aa);width:100%;min-height:46px;color:inherit;font:inherit;text-align:left;background:0 0;border:0;grid-template-columns:28px minmax(0,1fr) auto 16px;align-items:center;gap:10px;padding:7px 12px;display:grid}.activity-row.interactive{cursor:pointer}.activity-row.interactive:hover{background:var(--tool-card-hover-bg)}.activity-row.interactive:focus-visible{outline:2px solid color-mix(in srgb, var(--activity-accent) 68%, transparent);outline-offset:-2px}.activity-row.child{padding-left:34px;position:relative}.activity-row.child:before{background:color-mix(in srgb, var(--activity-accent) 26%, var(--tool-card-divider));content:"";width:1px;position:absolute;top:0;bottom:0;left:20px}.activity-row.child+.activity-row.child{border-top:1px solid color-mix(in srgb, var(--tool-card-divider) 52%, transparent)}.activity-row.kind-read{--activity-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 32%, #06b6d4 68%)}.activity-row.kind-write{--activity-accent:var(--color-success-text,#6fda83)}.activity-row.kind-edit,.activity-row.kind-rename{--activity-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 28%, #d99742 72%)}.activity-row.kind-delete{--activity-accent:var(--color-danger-text,#ee7676)}.activity-row.kind-shell{--activity-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 42%, #64748b 58%)}.activity-row.kind-shell-result{--activity-accent:color-mix(in srgb, var(--color-success-text,#6fda83) 72%, var(--color-text-secondary,#b6b6bd))}.activity-row.kind-capability{--activity-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 34%, #3b82f6 66%)}.activity-row.kind-batch{--activity-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 34%, #8b5cf6 66%)}.activity-row.phase-executing{--activity-phase:var(--color-text-info,#38bdf8)}.activity-row.phase-returned{--activity-phase:var(--color-warning-text,#e6b566)}.activity-row.phase-done{--activity-phase:var(--color-success-text,#6fda83)}.activity-row.phase-error{--activity-phase:var(--color-danger-text,#ee7676)}.activity-row.shell-result{box-shadow:inset 2px 0 0 color-mix(in srgb, var(--activity-accent) 68%, transparent)}.activity-icon{border:1px solid color-mix(in srgb, var(--activity-accent) 18%, transparent);background:color-mix(in srgb, var(--activity-accent) 9%, transparent);width:28px;height:28px;color:var(--activity-accent);border-radius:7px;place-items:center;display:grid}.activity-icon-svg{stroke-width:1.8px;width:15px;height:15px}.activity-main{gap:4px;min-width:0;display:grid}.activity-title-line{align-items:baseline;gap:8px;min-width:0;display:flex}.activity-title{color:var(--color-text-primary,#f5f5f6);font-size:var(--font-text-sm-size,12px);flex:none;font-weight:600;line-height:1.35}.activity-member{color:var(--color-text-secondary,#d4d4d8);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,10px);flex:none;line-height:1.4}.activity-target{min-width:0;color:var(--color-text-tertiary,#a3a3aa);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,11px);text-overflow:ellipsis;white-space:nowrap;line-height:1.4;overflow:hidden}.activity-meta{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,10px);font-variant-numeric:tabular-nums;white-space:nowrap;justify-content:flex-end;align-items:center;gap:8px;display:inline-flex}.activity-phase{color:var(--activity-phase);align-items:center;gap:5px;display:inline-flex}.activity-phase:before{content:"";background:currentColor;border-radius:9999px;width:6px;height:6px}.activity-progress-wrap{grid-template-columns:auto minmax(48px,110px);align-items:center;gap:8px;max-width:220px;display:grid}.activity-progress-counts{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,10px);font-variant-numeric:tabular-nums;white-space:nowrap}.activity-progress-track{background:color-mix(in srgb, var(--activity-accent) 14%, var(--tool-card-divider));border-radius:9999px;height:3px;display:block;overflow:hidden}.activity-progress-fill{border-radius:inherit;background:var(--activity-accent);height:100%;display:block}.activity-empty,.activity-refresh-error{color:var(--color-text-secondary,#b7b7bf);font-size:var(--font-text-sm-size,12px);padding:12px}.activity-refresh-error{border-top:1px solid var(--tool-card-divider);color:var(--color-danger-text,#ee7676)}.activity-detail-chevron,.activity-detail-spacer,.activity-detail-chevron.chevron{width:16px;height:16px}.activity-detail-chevron .icon-svg{width:13px;height:13px}.activity-entry.expanded>.activity-row{background:color-mix(in srgb, var(--activity-accent) 6%, transparent)}.activity-detail{border-top:1px solid color-mix(in srgb, var(--tool-card-divider) 72%, transparent);background:color-mix(in srgb, var(--color-background-primary,#101114) 90%, transparent);display:grid}.activity-detail-section+.activity-detail-section{border-top:1px solid color-mix(in srgb, var(--tool-card-divider) 62%, transparent)}.activity-detail-label{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,10px);letter-spacing:.02em;padding:8px 12px 0;font-weight:600}.activity-detail-section.error .activity-detail-label,.activity-detail-section.error .activity-detail-value{color:var(--color-danger-text,#ee7676)}.activity-detail-value{max-height:260px;color:var(--color-text-secondary,#c7c7ce);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,11px);white-space:pre-wrap;overflow-wrap:break-word;margin:0;padding:6px 12px 10px;line-height:1.5;overflow:auto}.activity-detail-status{color:var(--color-text-secondary,#b7b7bf);font-size:var(--font-text-sm-size,12px);padding:10px 12px}.activity-detail-status.error{color:var(--color-danger-text,#ee7676)}.activity-terminal{background:var(--color-background-primary,#101114)}.activity-terminal-command,.activity-terminal-output{color:var(--color-text-primary,#f5f5f6);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,11px);white-space:pre-wrap;overflow-wrap:break-word;background:0 0;border:0;border-radius:0;margin:0;line-height:1.55}.activity-terminal-command{border-bottom:1px solid color-mix(in srgb, var(--tool-card-divider) 74%, transparent);color:var(--color-text-secondary,#c7c7ce);padding:9px 12px}.activity-terminal-output{max-height:320px;padding:10px 12px;overflow:auto}.activity-terminal-meta{border-top:1px solid color-mix(in srgb, var(--tool-card-divider) 74%, transparent);color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-xs-size,10px);font-variant-numeric:tabular-nums;padding:7px 12px}.activity-terminal-meta.status-running{color:var(--color-text-info,#38bdf8)}.activity-terminal-meta.status-done{color:var(--color-success-text,#6fda83)}.activity-terminal-meta.status-failed{color:var(--color-danger-text,#ee7676)}@media (width<=520px){.activity-panel-header{grid-template-columns:12px minmax(0,1fr) auto 18px;gap:8px;min-height:54px;padding:8px 10px}.activity-panel-subtitle,.activity-panel-count{font-size:10px}.activity-row{grid-template-columns:26px minmax(0,1fr) 18px;gap:6px 8px;min-height:48px;padding:8px 10px}.activity-row.child{padding-left:28px}.activity-row.child:before{left:16px}.activity-icon{align-self:start;width:26px;height:26px}.activity-title-line{gap:2px;display:grid}.activity-meta{grid-column:2;justify-content:flex-start}.activity-detail-chevron,.activity-detail-spacer{grid-area:1/3/span 2}.activity-progress-wrap{grid-template-columns:auto minmax(40px,1fr);max-width:none}}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;font-family:var(--font-sans,ui-sans-serif, system-ui, sans-serif);color:var(--color-text-primary,#f5f5f6);--tool-card-border:color-mix(in srgb, var(--color-border-primary,#414141) 74%, transparent);--tool-card-header-bg:color-mix(in srgb, var(--color-background-secondary,#272727) 88%, transparent);--tool-card-body-bg:color-mix(in srgb, var(--color-background-primary,#181818) 94%, transparent);--tool-card-hover-bg:color-mix(in srgb, var(--color-background-tertiary,#343434) 46%, transparent);--tool-card-divider:color-mix(in srgb, var(--color-border-primary,#414141) 66%, transparent);--tool-accent:var(--color-text-secondary,#b6b6bd);--scrollbar-thumb:color-mix(in srgb, var(--color-text-tertiary,#8a8a8a) 56%, transparent);--scrollbar-thumb-hover:color-mix(in srgb, var(--color-text-secondary,#a8a8a8) 82%, transparent);background:0 0}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}*{box-sizing:border-box}html,body{background:0 0;margin:0;overflow:hidden}.shell{width:100%;padding:0;overflow:hidden}.empty,.tool-card{--tool-accent-soft:color-mix(in srgb, var(--tool-accent) 12%, transparent);border:1px solid var(--tool-card-border);background:var(--tool-card-header-bg);width:100%;box-shadow:none;color:var(--color-text-primary,#f5f5f6);border-radius:12px;overflow:hidden}.tool-card.workspace,.tool-card.directory{--tool-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 34%, #3b82f6 66%)}.tool-card.read,.tool-card.search{--tool-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 32%, #06b6d4 68%)}.tool-card.write{--tool-accent:var(--color-success-text,#6fda83)}.tool-card.edit,.tool-card.review{--tool-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 28%, #d99742 72%)}.tool-card.delete{--tool-accent:var(--color-danger-text,#ee7676)}.tool-card.shell{--tool-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 42%, #64748b 58%)}.tool-card.state-success{--tool-accent:var(--color-success-text,#6fda83)}.tool-card.state-error{--tool-accent:var(--color-danger-text,#ee7676)}.tool-card.state-running{--tool-accent:color-mix(in srgb, var(--color-text-primary,#f5f5f6) 30%, #38bdf8 70%)}@supports selector(::-webkit-scrollbar){.pretty-scrollbar::-webkit-scrollbar{width:12px;height:12px}.pretty-scrollbar::-webkit-scrollbar-button{width:0;height:0;display:none}.pretty-scrollbar::-webkit-scrollbar-track{background:0 0}.pretty-scrollbar::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb);background-clip:content-box;border:4px solid #0000;border-radius:9999px}.pretty-scrollbar::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover)}.pretty-scrollbar::-webkit-scrollbar-thumb:active{background-color:var(--scrollbar-thumb-hover)}.pretty-scrollbar::-webkit-scrollbar-corner{background:0 0}}.empty{color:var(--color-text-secondary,#b6b6bd);font-size:var(--font-text-sm-size,13px);padding:14px 16px}.tool-header{width:100%;min-height:64px;color:inherit;cursor:pointer;text-align:left;background:0 0;border:0;border-radius:11px;grid-template-columns:40px minmax(0,1fr) auto 20px;align-items:center;gap:12px;padding:10px 12px;display:grid}.tool-header:focus-visible,.review-diff-file-header:focus-visible,.review-more:focus-visible{outline:2px solid color-mix(in srgb, var(--tool-accent) 72%, transparent);outline-offset:-2px}.tool-header:hover:not(:disabled){background:var(--tool-card-hover-bg)}.tool-header:disabled{cursor:default}.tool-icon{border:1px solid color-mix(in srgb, var(--tool-accent) 18%, transparent);background:var(--tool-accent-soft);width:40px;height:40px;color:var(--tool-accent);border-radius:10px;place-items:center;display:grid}.icon-svg{stroke-width:1.8px;width:20px;height:20px;display:block}.tool-main{gap:2px;min-width:0;display:grid}.tool-title{color:var(--color-text-primary,#f5f5f6);font-size:var(--font-text-sm-size,14px);font-weight:550;line-height:1.3}.tool-label{color:var(--color-text-tertiary,#a3a3aa);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,12px);text-overflow:ellipsis;white-space:nowrap;line-height:1.4;overflow:hidden}.stats{font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-xs-size,12px);font-variant-numeric:tabular-nums;white-space:nowrap;align-items:center;gap:5px;display:inline-flex}.header-meta{color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-sm-size,12px);text-align:right;white-space:nowrap;line-height:1.35}.header-meta.empty{width:0}.add{color:var(--color-success-text,#6fda83)}.remove{color:var(--color-danger-text,#ee7676)}.chevron{width:20px;height:20px;color:var(--color-text-tertiary,#a3a3aa);border-radius:7px;place-items:center;transition:background .14s,color .14s,transform .14s;display:grid}.tool-header:hover:not(:disabled) .chevron{color:var(--color-text-primary,#f5f5f6)}.chevron .icon-svg{width:15px;height:15px}.chevron.expanded{transform:rotate(180deg)}.chevron.loading{transform:none}.chevron.loading .icon-svg{fill:none;stroke-linecap:round;stroke-dasharray:38 14;animation:.7s linear infinite payload-spinner}@keyframes payload-spinner{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.chevron.loading .icon-svg{animation:none}}.tool-body{border-top:1px solid var(--tool-card-divider);background:var(--tool-card-body-bg)}.workspace-details{max-height:420px;display:grid;overflow:auto}.workspace-rows{padding:4px 0;display:grid}.workspace-row{grid-template-columns:22px minmax(116px,.24fr) minmax(0,1fr);align-items:center;gap:10px;min-height:40px;padding:7px 12px;display:grid}.workspace-row-icon{background:color-mix(in srgb, var(--tool-accent) 9%, transparent);width:22px;height:22px;color:color-mix(in srgb, var(--tool-accent) 72%, var(--color-text-tertiary,#a3a3aa));border-radius:6px;place-items:center;display:grid}.workspace-row-icon-svg{stroke-width:1.8px;width:14px;height:14px}.workspace-key{min-height:22px;color:var(--color-text-tertiary,#a3a3aa);font-size:var(--font-text-sm-size,12px);align-items:center;font-weight:500;display:flex}.workspace-value{min-width:0;color:var(--color-text-secondary,#c7c7ce);font-size:var(--font-text-sm-size,12px);text-overflow:ellipsis;white-space:nowrap;line-height:1.45;overflow:hidden}.workspace-value.mono{font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace)}.workspace-base-value{align-items:center;gap:7px;min-width:0;display:flex}.workspace-base-value .workspace-value{flex:0 auto}.workspace-base-warning{width:18px;height:18px;color:var(--color-warning-text,#e6b566);cursor:help;flex:none;place-items:center;display:grid}.workspace-base-warning-svg{stroke-width:2px;width:14px;height:14px}.workspace-chip-list{flex-wrap:nowrap;align-items:center;gap:6px;min-width:0;display:flex;overflow:hidden}.workspace-chip{border:1px solid color-mix(in srgb, var(--tool-accent) 16%, var(--tool-card-divider));background:color-mix(in srgb, var(--tool-accent) 7%, transparent);max-width:100%;min-height:24px;color:var(--color-text-secondary,#c7c7ce);text-overflow:ellipsis;white-space:nowrap;border-radius:9999px;flex:none;align-items:center;gap:5px;padding:3px 8px;font-size:11px;line-height:1.25;display:inline-flex;overflow:hidden}.workspace-chip-logo{object-fit:contain;flex:none;width:13px;height:13px;display:block}.workspace-chip-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.workspace-agent-profile{border:0;border-bottom:1px solid color-mix(in srgb, var(--tool-accent) 34%, var(--tool-card-divider));max-width:100%;min-height:24px;color:var(--color-text-secondary,#c7c7ce);white-space:nowrap;background:0 0;border-radius:0;align-items:center;gap:5px;padding:3px 2px 4px;font-size:11px;line-height:1.25;display:inline-flex;overflow:hidden}.workspace-agent-profile-logo{object-fit:contain;flex:none;width:14px;height:14px;display:block}.workspace-agent-profile:hover{border-bottom-color:var(--tool-accent);color:var(--color-text-primary,#f5f5f6)}.workspace-agent-profile.muted{color:var(--color-text-tertiary,#a3a3aa);opacity:.72;border-bottom-style:dashed}.workspace-provider-logo{cursor:help;flex:none;place-items:center;width:20px;height:24px;display:inline-grid}.workspace-provider-logo-image{object-fit:contain;width:16px;height:16px;display:block}.workspace-provider-logo.muted{opacity:.62}.workspace-chip.muted{color:var(--color-text-tertiary,#a3a3aa);opacity:.72;border-style:dashed}.workspace-skills-row,.workspace-instructions-row,.workspace-agents-row{align-items:start}.workspace-skills-list,.workspace-agents-list{flex-wrap:wrap;overflow:visible}.workspace-instruction-status{border-radius:5px;flex:none;place-items:center;width:18px;height:18px;display:grid}.workspace-instruction-status.loaded{background:color-mix(in srgb, var(--color-success-text,#6fda83) 12%, transparent);color:var(--color-success-text,#6fda83)}.workspace-instruction-status.available{background:color-mix(in srgb, var(--color-text-tertiary,#a3a3aa) 9%, transparent);color:var(--color-text-tertiary,#a3a3aa)}.workspace-instruction-status-svg{stroke-width:1.9px;width:12px;height:12px}.workspace-instruction-list{border:1px solid var(--tool-card-divider);background:color-mix(in srgb, var(--tool-card-body-bg) 88%, transparent);border-radius:9px;min-width:0;display:grid;overflow:hidden}.workspace-instructions-content{min-width:0;display:block}.workspace-instructions-toggle{border:0;border-top:1px solid var(--tool-card-divider);background:color-mix(in srgb, var(--tool-card-body-bg) 72%, transparent);width:100%;min-height:32px;color:var(--tool-accent);cursor:pointer;font:inherit;font-size:var(--font-text-sm-size,11px);white-space:nowrap;border-radius:0 0 9px 9px;justify-content:center;align-items:center;padding:6px 10px;font-weight:550;line-height:1.25;display:flex}.workspace-instructions-toggle:hover{background:var(--tool-card-hover-bg)}.workspace-instructions-toggle:focus-visible{outline:2px solid color-mix(in srgb, var(--tool-accent) 72%, transparent);outline-offset:2px}.workspace-instruction-item+.workspace-instruction-item{border-top:1px solid var(--tool-card-divider)}.workspace-instruction-header{width:100%;min-width:0;color:inherit;font:inherit;text-align:left;background:0 0;border:0;grid-template-columns:22px minmax(0,1fr) 18px;align-items:center;gap:9px;padding:8px 10px;display:grid}.workspace-instruction-header.interactive{cursor:pointer}.workspace-instruction-header.interactive:hover{background:var(--tool-card-hover-bg)}.workspace-instruction-header.interactive:focus-visible{outline:2px solid color-mix(in srgb, var(--tool-accent) 72%, transparent);outline-offset:-2px}.workspace-instruction-text{gap:2px;min-width:0;display:grid}.workspace-instruction-name{min-width:0;color:var(--color-text-primary,#f5f5f6);font-size:var(--font-text-sm-size,12px);text-overflow:ellipsis;white-space:nowrap;font-weight:550;overflow:hidden}.workspace-instruction-path{min-width:0;color:var(--color-text-tertiary,#a3a3aa);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);text-overflow:ellipsis;white-space:nowrap;font-size:10px;line-height:1.35;overflow:hidden}.workspace-instruction-chevron{width:18px;height:18px;color:var(--color-text-tertiary,#a3a3aa);place-items:center;transition:transform .14s;display:grid}.workspace-instruction-item.expanded .workspace-instruction-chevron{transform:rotate(180deg)}.workspace-instruction-chevron-svg{width:14px;height:14px}.workspace-instruction-preview{border-top:1px solid var(--tool-card-divider);background:color-mix(in srgb, var(--color-background-primary,#101114) 92%, transparent);max-height:300px;color:var(--color-text-secondary,#c7c7ce);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);white-space:pre-wrap;overflow-wrap:break-word;margin:0;padding:10px 12px;font-size:11px;line-height:1.55;overflow:auto}.workspace-instruction-preview[hidden]{display:none}.review-header{grid-template-columns:40px minmax(0,1fr) auto 20px}.review-title-group{gap:3px;min-width:0;display:grid}.review-summary{border-top:1px solid var(--tool-card-divider);background:var(--tool-card-body-bg);display:grid}.review-diff-file-stats{align-items:center;gap:8px;display:flex}.review-empty{color:var(--color-text-secondary,#b7b7bf);font-size:var(--font-text-sm-size,13px)}.review-more{border:0;border-top:1px solid var(--tool-card-divider);width:100%;min-height:40px;color:var(--color-text-tertiary,#a3a3aa);cursor:pointer;font:inherit;font-size:var(--font-text-sm-size,12px);text-align:left;background:0 0;padding:0 12px}.review-more:hover{background:var(--tool-card-hover-bg);color:var(--color-text-primary,#f5f5f6)}.review-diff{max-height:520px;display:grid;overflow:hidden auto}.review-diff-files{gap:0;padding:0;display:grid}.review-diff-file{border:0;border-radius:0;overflow:hidden}.review-diff-file+.review-diff-file{border-top:1px solid var(--tool-card-divider)}.review-diff-file-header{width:100%;min-height:42px;color:var(--color-text-primary,#f5f5f6);cursor:pointer;font:inherit;text-align:left;background:0 0;border:0;grid-template-columns:22px minmax(0,1fr) auto;align-items:center;gap:10px;padding:0 12px;display:grid}.review-file-kind{background:color-mix(in srgb, var(--color-text-tertiary,#a3a3aa) 10%, transparent);width:20px;height:20px;color:var(--color-text-tertiary,#a3a3aa);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);border-radius:6px;align-self:center;place-items:center;font-size:10px;font-weight:700;display:grid}.review-file-kind.added{background:color-mix(in srgb, var(--color-success-text,#6fda83) 12%, transparent);color:var(--color-success-text,#6fda83)}.review-file-kind.edited,.review-file-kind.renamed,.review-file-kind.renamed-edited{background:color-mix(in srgb, var(--color-warning-text,#e6b566) 12%, transparent);color:var(--color-warning-text,#e6b566)}.review-file-kind.deleted{background:color-mix(in srgb, var(--color-danger-text,#ee7676) 12%, transparent);color:var(--color-danger-text,#ee7676)}.review-single-file{overflow:hidden}.review-diff-file-header:hover{background:var(--tool-card-hover-bg)}.review-diff-file-name,.review-diff-file-stats{text-overflow:ellipsis;white-space:nowrap;font-size:13px;line-height:20px;overflow:hidden}.review-diff-file-name{font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace)}.review-diff-file-name.renamed{text-overflow:clip;align-items:center;gap:6px;min-width:0;display:flex}.review-diff-file-path{text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:calc(50% - 10px);overflow:hidden}.review-diff-file-path.previous{color:var(--color-text-tertiary,#a3a3aa)}.review-diff-file-path.current{color:var(--color-text-primary,#f5f5f6)}.review-diff-file-arrow{color:var(--color-text-tertiary,#a3a3aa);font-family:var(--font-sans,ui-sans-serif, system-ui, sans-serif);flex:none}.review-diff-file-stats{font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-sm-size,12px);font-variant-numeric:tabular-nums;justify-content:flex-end;overflow:visible}.status{font-size:var(--font-text-sm-size,12px);padding:10px 12px}.status.muted{color:var(--color-text-secondary,#b7b7bf)}.status.error{color:var(--color-danger-text,#ee7676)}.pierre-diff,.pierre-file{--diffs-bg:var(--tool-payload-bg,var(--color-background-primary,#101114));--diffs-light-bg:var(--color-background-primary,#fff);--diffs-dark-bg:var(--tool-payload-bg,var(--color-background-primary,#101114));--diffs-font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);--diffs-header-font-family:var(--font-sans,ui-sans-serif, system-ui, sans-serif);--diffs-font-size:var(--font-text-sm-size,12px);--diffs-line-height:20px;border-bottom-right-radius:8px;border-bottom-left-radius:8px;max-height:420px;display:block;overflow:auto}.text-payload{max-height:420px;color:var(--color-text-secondary,#c7c7ce);font-family:var(--font-mono,ui-monospace, SFMono-Regular, monospace);font-size:var(--font-text-sm-size,12px);white-space:pre-wrap;overflow-wrap:break-word;margin:0;padding:10px 12px;line-height:1.55;overflow:auto}.text-payload.bash{color:var(--color-text-primary,#f5f5f6);background:var(--color-background-primary,#101114)}@media (width<=520px){.tool-header{grid-template-columns:36px minmax(0,1fr) auto 18px;gap:9px;min-height:58px;padding:9px 10px}.tool-icon{border-radius:9px;width:36px;height:36px}.chevron{width:18px;height:18px}.review-header{grid-template-columns:36px minmax(0,1fr) auto 18px}.workspace-row{grid-template-columns:22px minmax(0,1fr);gap:2px 8px;padding-block:8px}.workspace-row-icon{grid-row:1/span 2;align-self:start}.workspace-row>.workspace-key,.workspace-row>.workspace-value,.workspace-row>.workspace-chip-list,.workspace-row>.workspace-instructions-content{grid-column:2}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./workspace-app-CwbJnb_w.js";import"./workspace-app-BztEvZIC.js";document.documentElement.dataset.forgerelayApp=`historical-tool-card`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./workspace-app-CwbJnb_w.js";import"./workspace-app-BztEvZIC.js";document.documentElement.dataset.forgerelayApp=`workspace-lifecycle-compatibility`;
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>ForgeRelay Diff</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/workspace-app-
|
|
7
|
+
<script type="module" crossorigin src="./assets/workspace-app-rKuhdae8.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="./assets/chunk-EyZ2wyi3.js">
|
|
9
|
-
<link rel="modulepreload" crossorigin href="./assets/workspace-app-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="./assets/workspace-app-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="./assets/workspace-app-
|
|
9
|
+
<link rel="modulepreload" crossorigin href="./assets/workspace-app-CwbJnb_w.js">
|
|
10
|
+
<link rel="modulepreload" crossorigin href="./assets/workspace-app-BztEvZIC.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="./assets/workspace-app-YnUST8IP.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<main id="app" class="shell">
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>ForgeRelay Workspace Lifecycle</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/workspace-lifecycle-app-
|
|
7
|
+
<script type="module" crossorigin src="./assets/workspace-lifecycle-app-CEfMdudP.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="./assets/chunk-EyZ2wyi3.js">
|
|
9
|
-
<link rel="modulepreload" crossorigin href="./assets/workspace-app-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="./assets/workspace-app-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="./assets/workspace-app-
|
|
9
|
+
<link rel="modulepreload" crossorigin href="./assets/workspace-app-CwbJnb_w.js">
|
|
10
|
+
<link rel="modulepreload" crossorigin href="./assets/workspace-app-BztEvZIC.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="./assets/workspace-app-YnUST8IP.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<main id="app" class="shell">
|
package/dist/user-config.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, } from "node:fs";
|
|
1
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { expandHomePath } from "./roots.js";
|
|
6
6
|
import { mergeHookConfigs, parseHookFile, } from "./hooks.js";
|
|
7
|
+
const AUTH_LOCK_RETRY_MS = 10;
|
|
8
|
+
const AUTH_LOCK_TIMEOUT_MS = 5_000;
|
|
9
|
+
const AUTH_LOCK_STALE_MS = 30_000;
|
|
10
|
+
const AUTH_LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
7
11
|
export function forgerelayConfigDir(env = process.env) {
|
|
8
12
|
const explicit = env.FORGERELAY_CONFIG_DIR ?? env.DEVSPACE_CONFIG_DIR;
|
|
9
13
|
if (explicit)
|
|
@@ -64,12 +68,81 @@ export function writeForgeRelayConfig(config, env = process.env) {
|
|
|
64
68
|
export function writeForgeRelayAuth(auth, env = process.env) {
|
|
65
69
|
const filePath = forgerelayAuthPath(env);
|
|
66
70
|
mkdirSync(forgerelayConfigDir(env), { recursive: true });
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
return withAuthFileLock(filePath, () => {
|
|
72
|
+
writeJsonFile(filePath, auth, 0o600);
|
|
73
|
+
return filePath;
|
|
74
|
+
});
|
|
69
75
|
}
|
|
70
76
|
export function generateOwnerToken() {
|
|
71
77
|
return randomBytes(32).toString("base64url");
|
|
72
78
|
}
|
|
79
|
+
export function generateInstanceId() {
|
|
80
|
+
return `forge-${randomUUID()}`;
|
|
81
|
+
}
|
|
82
|
+
export function ensureForgeRelayInstanceId(env = process.env) {
|
|
83
|
+
const existing = loadForgeRelayFiles(env).auth.instanceId?.trim();
|
|
84
|
+
if (existing)
|
|
85
|
+
return existing;
|
|
86
|
+
let resolved = "";
|
|
87
|
+
updateForgeRelayAuth((auth) => {
|
|
88
|
+
const current = auth.instanceId?.trim();
|
|
89
|
+
if (current) {
|
|
90
|
+
resolved = current;
|
|
91
|
+
return auth;
|
|
92
|
+
}
|
|
93
|
+
resolved = generateInstanceId();
|
|
94
|
+
return { ...auth, instanceId: resolved };
|
|
95
|
+
}, env);
|
|
96
|
+
return resolved;
|
|
97
|
+
}
|
|
98
|
+
function normalizeRemoteAlias(alias) {
|
|
99
|
+
const normalized = alias.trim();
|
|
100
|
+
if (!normalized || /\s/.test(normalized)) {
|
|
101
|
+
throw new Error("Remote alias must be a non-empty name without whitespace.");
|
|
102
|
+
}
|
|
103
|
+
return normalized;
|
|
104
|
+
}
|
|
105
|
+
export function writeForgeRelayRemote(alias, remote, env = process.env) {
|
|
106
|
+
const normalizedAlias = normalizeRemoteAlias(alias);
|
|
107
|
+
return updateForgeRelayAuth((auth) => {
|
|
108
|
+
const remotes = { ...(auth.remotes ?? {}) };
|
|
109
|
+
const existing = remotes[normalizedAlias];
|
|
110
|
+
if (existing && existing.instanceId !== remote.instanceId) {
|
|
111
|
+
throw new Error(`Remote alias ${normalizedAlias} already belongs to another ForgeRelay instance.`);
|
|
112
|
+
}
|
|
113
|
+
const duplicate = Object.entries(remotes).find(([name, record]) => name !== normalizedAlias && record.instanceId === remote.instanceId);
|
|
114
|
+
if (duplicate) {
|
|
115
|
+
throw new Error(`ForgeRelay instance is already registered as ${duplicate[0]}; rename that remote instead.`);
|
|
116
|
+
}
|
|
117
|
+
remotes[normalizedAlias] = remote;
|
|
118
|
+
return { ...auth, remotes };
|
|
119
|
+
}, env);
|
|
120
|
+
}
|
|
121
|
+
export function renameForgeRelayRemote(fromAlias, toAlias, env = process.env) {
|
|
122
|
+
const from = normalizeRemoteAlias(fromAlias);
|
|
123
|
+
const to = normalizeRemoteAlias(toAlias);
|
|
124
|
+
return updateForgeRelayAuth((auth) => {
|
|
125
|
+
const remotes = { ...(auth.remotes ?? {}) };
|
|
126
|
+
const remote = remotes[from];
|
|
127
|
+
if (!remote)
|
|
128
|
+
throw new Error(`Unknown remote alias: ${from}`);
|
|
129
|
+
if (from !== to && remotes[to])
|
|
130
|
+
throw new Error(`Remote alias already exists: ${to}`);
|
|
131
|
+
delete remotes[from];
|
|
132
|
+
remotes[to] = remote;
|
|
133
|
+
return { ...auth, remotes };
|
|
134
|
+
}, env);
|
|
135
|
+
}
|
|
136
|
+
export function removeForgeRelayRemote(alias, env = process.env) {
|
|
137
|
+
const normalizedAlias = normalizeRemoteAlias(alias);
|
|
138
|
+
return updateForgeRelayAuth((auth) => {
|
|
139
|
+
const remotes = { ...(auth.remotes ?? {}) };
|
|
140
|
+
if (!remotes[normalizedAlias])
|
|
141
|
+
throw new Error(`Unknown remote alias: ${normalizedAlias}`);
|
|
142
|
+
delete remotes[normalizedAlias];
|
|
143
|
+
return { ...auth, remotes };
|
|
144
|
+
}, env);
|
|
145
|
+
}
|
|
73
146
|
export function ensureForgeRelayDefaultSkills(env = process.env) {
|
|
74
147
|
const targetPath = join(forgerelaySkillsDir(env), "subagent-delegation", "SKILL.md");
|
|
75
148
|
if (existsSync(targetPath))
|
|
@@ -124,6 +197,59 @@ function readJsonFile(filePath) {
|
|
|
124
197
|
throw new Error(`Unable to read ${filePath}: ${reason}`);
|
|
125
198
|
}
|
|
126
199
|
}
|
|
200
|
+
function updateForgeRelayAuth(update, env) {
|
|
201
|
+
const filePath = forgerelayAuthPath(env);
|
|
202
|
+
mkdirSync(forgerelayConfigDir(env), { recursive: true });
|
|
203
|
+
return withAuthFileLock(filePath, () => {
|
|
204
|
+
const auth = existsSync(filePath) ? readJsonFile(filePath) : {};
|
|
205
|
+
writeJsonFile(filePath, update(auth), 0o600);
|
|
206
|
+
return filePath;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function withAuthFileLock(filePath, operation) {
|
|
210
|
+
const lockPath = `${filePath}.lock`;
|
|
211
|
+
const deadline = Date.now() + AUTH_LOCK_TIMEOUT_MS;
|
|
212
|
+
for (;;) {
|
|
213
|
+
try {
|
|
214
|
+
const fd = openSync(lockPath, "wx", 0o600);
|
|
215
|
+
closeSync(fd);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
const code = error.code;
|
|
220
|
+
if (code !== "EEXIST")
|
|
221
|
+
throw error;
|
|
222
|
+
try {
|
|
223
|
+
if (Date.now() - statSync(lockPath).mtimeMs > AUTH_LOCK_STALE_MS) {
|
|
224
|
+
rmSync(lockPath, { force: true });
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (statError) {
|
|
229
|
+
if (statError.code === "ENOENT")
|
|
230
|
+
continue;
|
|
231
|
+
throw statError;
|
|
232
|
+
}
|
|
233
|
+
if (Date.now() >= deadline) {
|
|
234
|
+
throw new Error(`Timed out waiting for ForgeRelay auth lock: ${lockPath}`);
|
|
235
|
+
}
|
|
236
|
+
Atomics.wait(AUTH_LOCK_SLEEP, 0, 0, AUTH_LOCK_RETRY_MS);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
return operation();
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
rmSync(lockPath, { force: true });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
127
246
|
function writeJsonFile(filePath, value, mode) {
|
|
128
|
-
|
|
247
|
+
const tempPath = `${filePath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
248
|
+
try {
|
|
249
|
+
writeFileSync(tempPath, JSON.stringify(value, null, 2) + "\n", { mode });
|
|
250
|
+
renameSync(tempPath, filePath);
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
rmSync(tempPath, { force: true });
|
|
254
|
+
}
|
|
129
255
|
}
|
package/docs/configuration.md
CHANGED
|
@@ -305,6 +305,25 @@ switching a logical workspace therefore does not by itself cause unchanged proje
|
|
|
305
305
|
context to be injected again, while changed context produces a new fingerprint and
|
|
306
306
|
is delivered on the next `auto` open.
|
|
307
307
|
|
|
308
|
+
Composite Workspaces use the same `open_workspace` entry point with
|
|
309
|
+
`kind="composite"` and a human-readable `name`. They have no filesystem root of
|
|
310
|
+
their own and may contain zero or more named members. Member management also stays
|
|
311
|
+
on `open_workspace` through `action="member"`; an added member either references an
|
|
312
|
+
existing `workspaceId` or reuses the normal local/managed/relay Workspace open
|
|
313
|
+
definition (`path`, optional `relay`, `mode`, and worktree options). Every Composite
|
|
314
|
+
open returns its `kind`, name, and member purposes regardless of
|
|
315
|
+
`context="auto"|"full"|"none"`. To receive one member's heavy project bootstrap,
|
|
316
|
+
reopen the Composite with `memberName=<name>`; this does not create an implicit
|
|
317
|
+
current member. Core work calls against a Composite require an explicit `member`
|
|
318
|
+
and retain the `cws_...` ID as the Host-facing Workspace identity.
|
|
319
|
+
|
|
320
|
+
Composite member purpose text is descriptive only. ForgeRelay never routes by tool
|
|
321
|
+
type, hardware role, load, or availability, and a missing/offline member never falls
|
|
322
|
+
back to another member or local execution. Member filesystems, Git state, processes,
|
|
323
|
+
Hooks, Skills, language services, and Activity facts remain owned by the underlying
|
|
324
|
+
Workspace. The Composite Activity Panel only aggregates their presentation into one
|
|
325
|
+
Host Turn.
|
|
326
|
+
|
|
308
327
|
Use `open_workspace(action="list")` only when the Agent needs to continue an older
|
|
309
328
|
logical workspace, choose among multiple handles, or organize workspace state. The
|
|
310
329
|
inventory is paginated (50 records by default, at most 100) and can filter by
|
|
@@ -321,6 +340,10 @@ or externally removed managed-worktree root can therefore remain diagnostically
|
|
|
321
340
|
files. For a managed-worktree-backed workspace, `close_workspace` requires
|
|
322
341
|
`commitMessage` and runs the existing safe worktree finalize lifecycle: close Hooks,
|
|
323
342
|
commit when needed, fast-forward-only integration, cleanup, and alias invalidation.
|
|
343
|
+
For a Composite Workspace, the same tool means dissolve: it removes only the
|
|
344
|
+
Composite identity, membership, and Composite-facing panel/lifecycle state. It does
|
|
345
|
+
not close members, finalize member worktrees, interrupt member processes, delete
|
|
346
|
+
files, or remove remote registrations and relayed Workspace routes.
|
|
324
347
|
|
|
325
348
|
Hot workspace/session activity timestamps are coalesced in memory and flushed to the
|
|
326
349
|
SQLite state database in a transaction at most every five minutes; normal shutdown
|
|
@@ -386,7 +409,7 @@ Hooks v1 是自动生命周期规则。规则由用户或 Agent 主动写入;
|
|
|
386
409
|
<workspace>/.forgerelay/hooks/<hook-name>.json
|
|
387
410
|
```
|
|
388
411
|
|
|
389
|
-
文件名去掉 `.json` 后就是 Hook 名,也是日志和 Agent-visible report 中显示的名称。例如 `release-tag-
|
|
412
|
+
文件名去掉 `.json` 后就是 Hook 名,也是日志和 Agent-visible report 中显示的名称。例如 `release-tag-gate.json` 会显示为 `release-tag-gate`。目录内按文件名字典序执行;需要显式排序时可以使用 `10-release-verify.json`、`20-package-inspection.json` 这样的前缀。ForgeRelay 只读取普通 `*.json` 文件,所以临时停用某条 Hook 时可以把扩展名改掉。
|
|
390
413
|
|
|
391
414
|
全局 Hook 在 server 启动时读取,修改后需要重启 ForgeRelay;项目目录在每次事件时重新读取,所以 Agent 修改项目 Hook 后不需要重启。全局规则先执行,项目规则随后执行,两边都只做追加,不互相覆盖。
|
|
392
415
|
|
|
@@ -405,7 +428,7 @@ Hooks v1 是自动生命周期规则。规则由用户或 Agent 主动写入;
|
|
|
405
428
|
}
|
|
406
429
|
```
|
|
407
430
|
|
|
408
|
-
这个例子可以保存为 `.forgerelay/hooks/release-tag-
|
|
431
|
+
这个例子可以保存为 `.forgerelay/hooks/release-tag-gate.json`。Agent 通过 ForgeRelay `bash` 请求推送稳定版本 tag 时,Hook 只执行轻量仓库状态门禁:拒绝 force/delete 形式,校验 clean working tree(含 untracked)、tag 与 package version 一致,并要求本地 tag 指向当前 HEAD。Hook 不运行、也不要求本地 CI;tag 推送后由 GitHub Actions 的 Linux/macOS/Windows 矩阵执行权威验证,全部通过后发布 job 才会继续。`npm run release:verify` 仅用于需要时本地复现云端环境。
|
|
409
432
|
|
|
410
433
|
独立 Hook 文件支持这些顶层字段:
|
|
411
434
|
|
|
@@ -472,7 +495,7 @@ Matcher 匹配 ForgeRelay 收到的那次 tool request,不会窥探该命令
|
|
|
472
495
|
|
|
473
496
|
```text
|
|
474
497
|
Hook results:
|
|
475
|
-
✓ release-tag-
|
|
498
|
+
✓ release-tag-gate (BeforeTool, project) passed in 42ms
|
|
476
499
|
```
|
|
477
500
|
|
|
478
501
|
阻断失败会明确显示 `failed`。ForgeRelay 的 server instructions 要求 Agent 在出现 Hook results 时,向用户说明有意义的 Hook 是否通过或阻断了操作。异步 subagent 的 `SubagentStart` / `SubagentStop` 报告会随 session 持久化,并由 `forgerelay agents show` 展示。
|
package/docs/debugging.md
CHANGED
|
@@ -50,6 +50,13 @@ To relocate the entire interactive debug config explicitly, use:
|
|
|
50
50
|
FORGERELAY_DEBUG_CONFIG_DIR=/path/to/debug-config npm run dev
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
The selected debug config is authoritative for its own `host` and `port`; the
|
|
54
|
+
launcher reports endpoints derived from that file instead of assuming `7677`.
|
|
55
|
+
This makes parallel isolated instances straightforward: give each instance its
|
|
56
|
+
own config, state, and worktree directories and configure different ports (for
|
|
57
|
+
example `7677` for a Gateway and `6768` for an Execution ForgeRelay), then launch
|
|
58
|
+
each with its own `FORGERELAY_DEBUG_CONFIG_DIR`.
|
|
59
|
+
|
|
53
60
|
## Run a local MCP Apps Host
|
|
54
61
|
|
|
55
62
|
Use MCP Inspector as the fast local Host for MCP App/resource iteration:
|
package/docs/versioning.md
CHANGED
|
@@ -79,25 +79,20 @@ The bump commands update:
|
|
|
79
79
|
|
|
80
80
|
They do not commit, tag, push, or publish.
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
For local diagnosis or cloud-parity reproduction, you can optionally run:
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
85
|
npm run release:verify
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
`release:verify`
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
performs its own `npm ci` so native addons use the same Node ABI as cloud CI,
|
|
93
|
-
then reruns the LSP/release tests most sensitive to event-loop timing, process
|
|
94
|
-
lifecycle, path canonicalization, executable discovery, and cleanup behavior.
|
|
95
|
-
It also tests that a command which exists on `PATH` but fails its `--version`
|
|
96
|
-
preflight is treated as unavailable rather than as an installed Language server.
|
|
88
|
+
`release:verify` runs the same focused parity checks in an isolated Node 22.19.0 sandbox
|
|
89
|
+
and records a local proof under `.git/forgerelay/` for debugging/audit purposes. It is
|
|
90
|
+
not a prerequisite for pushing a release tag. The authoritative release verification
|
|
91
|
+
is the tag-triggered GitHub Actions matrix on Linux, macOS, and Windows.
|
|
97
92
|
|
|
98
|
-
Cloud verification and the publication job are
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
Cloud verification and the publication job are pinned to Node 22.19.0, the minimum
|
|
94
|
+
supported Node release. The optional local parity tool uses the same runtime so a cloud
|
|
95
|
+
failure can be reproduced locally without making local execution part of the release gate.
|
|
101
96
|
|
|
102
97
|
Validate a specific tag with:
|
|
103
98
|
|
|
@@ -172,11 +167,8 @@ npm publishing token.
|
|
|
172
167
|
3. Run the appropriate `release:patch`, `release:minor`, or `release:major`
|
|
173
168
|
command.
|
|
174
169
|
4. Review the generated version and changelog diff, then commit the release-ready code and metadata.
|
|
175
|
-
5.
|
|
176
|
-
|
|
177
|
-
pushes do not need to run the full release gate.
|
|
178
|
-
6. Push the verified `main` commit without changing it afterward.
|
|
179
|
-
7. Create the exact version tag, for example:
|
|
170
|
+
5. Push the release-ready `main` commit without changing it afterward.
|
|
171
|
+
6. Create the exact version tag, for example:
|
|
180
172
|
|
|
181
173
|
```bash
|
|
182
174
|
git tag v0.2.0
|
|
@@ -185,7 +177,7 @@ npm publishing token.
|
|
|
185
177
|
|
|
186
178
|
The tag push is the publication action. The release workflow publishes npm only after cloud CI passes, then extracts the matching `CHANGELOG.md` release section as the GitHub Release body. Keep `Unreleased` user-facing and structured (`Added`, `Changed`, `Fixed`, `Security`) because those notes are what users see on the Release page.
|
|
187
179
|
|
|
188
|
-
Project release Hooks match the stable tag-push command as a substring of the ForgeRelay shell request. A compound command is allowed: when `commandRegex` matches `git push origin vX.Y.Z`, the Hook receives that matched command as `FORGERELAY_HOOK_PAYLOAD.command` and retains the complete shell request as `originalCommand` when they differ. The release Hook
|
|
180
|
+
Project release Hooks match the stable tag-push command as a substring of the ForgeRelay shell request. A compound command is allowed: when `commandRegex` matches `git push origin vX.Y.Z`, the Hook receives that matched command as `FORGERELAY_HOOK_PAYLOAD.command` and retains the complete shell request as `originalCommand` when they differ. The release Hook is intentionally lightweight: it rejects force/deletion forms, requires a clean working tree including untracked files, requires the tag to match the package version, and requires the local tag to resolve to current HEAD. It does not run or require local CI. The pushed tag then starts the authoritative Linux/macOS/Windows cloud verification; publication cannot run unless that matrix succeeds.
|
|
189
181
|
|
|
190
182
|
## Attribution guardrails
|
|
191
183
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akira-tl/forgerelay",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Local development control plane for MCP coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/Akira-TL/forgerelay#readme",
|
|
@@ -41,10 +41,13 @@
|
|
|
41
41
|
"debug:serve": "node scripts/debug/serve.mjs",
|
|
42
42
|
"debug:accept": "node scripts/debug/accept.mjs",
|
|
43
43
|
"lsp:interop": "node scripts/lsp-interop.mjs",
|
|
44
|
+
"ci:verify": "node scripts/ci/verify.mjs",
|
|
44
45
|
"release:parity": "node scripts/release-parity.mjs",
|
|
46
|
+
"release:pack": "node scripts/release/pack.mjs",
|
|
47
|
+
"release:publish": "node scripts/release/publish.mjs",
|
|
45
48
|
"postinstall": "node scripts/fix-node-pty-permissions.mjs",
|
|
46
49
|
"start": "node dist/cli.js serve",
|
|
47
|
-
"test": "node --test scripts/debug/runtime.test.mjs scripts/release-proof.test.mjs scripts/release/release-gate.test.mjs scripts/release/release-version.test.mjs && tsx src/oauth/router.test.ts && tsx src/config.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/normalization/hover.test.ts && tsx src/lsp/references.server.test.ts && tsx src/lsp/operations/document-symbols.server.test.ts && tsx src/lsp/operations/workspace-symbols.server.test.ts && tsx src/lsp/operations/diagnostics-push.server.test.ts && tsx src/lsp/operations/diagnostics-pull.server.test.ts && tsx src/lsp/operations/request-hardening.server.test.ts && tsx src/lsp/operations/recovery.server.test.ts && tsx src/lsp/operations/lifecycle.server.test.ts && tsx src/lsp/runtime/semantic-requests.test.ts && tsx src/logger.test.ts && tsx src/proxy-trust.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.test.ts && tsx src/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/workspace-lifecycle-app.test.ts && tsx src/ui/activity/model.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/operations/edit-preflight.test.ts && tsx src/skills.test.ts && tsx src/db/migrations.test.ts && tsx src/workspace-store.test.ts && tsx src/activity/audit-store.test.ts && tsx src/activity/bash-output-store.test.ts && tsx src/activity/lifecycle.test.ts && tsx src/activity/query-service.test.ts && tsx src/operations/core-operation-executor.test.ts && tsx src/operations/bulk-mutation.test.ts && tsx src/operations/batch/scheduler.test.ts && tsx src/operations/batch/executor-policy.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && npm run build:app && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
|
|
50
|
+
"test": "node --test scripts/debug/runtime.test.mjs scripts/release-proof.test.mjs scripts/release/release-gate.test.mjs scripts/release/release-version.test.mjs && tsx src/oauth/router.test.ts && tsx src/remote-auth-cli.test.ts && tsx src/remote-ssh-auth-cli.test.ts && tsx src/remote-workspace-relay.test.ts && tsx src/remote-workspace-relay-process.test.ts && tsx src/config.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/normalization/hover.test.ts && tsx src/lsp/references.server.test.ts && tsx src/lsp/operations/document-symbols.server.test.ts && tsx src/lsp/operations/workspace-symbols.server.test.ts && tsx src/lsp/operations/diagnostics-push.server.test.ts && tsx src/lsp/operations/diagnostics-pull.server.test.ts && tsx src/lsp/operations/request-hardening.server.test.ts && tsx src/lsp/operations/recovery.server.test.ts && tsx src/lsp/operations/lifecycle.server.test.ts && tsx src/lsp/runtime/semantic-requests.test.ts && tsx src/logger.test.ts && tsx src/proxy-trust.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.test.ts && tsx src/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/workspace-lifecycle-app.test.ts && tsx src/ui/activity/model.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/operations/edit-preflight.test.ts && tsx src/skills.test.ts && tsx src/db/migrations.test.ts && tsx src/workspace-store.test.ts && tsx src/activity/audit-store.test.ts && tsx src/activity/bash-output-store.test.ts && tsx src/activity/lifecycle.test.ts && tsx src/activity/query-service.test.ts && tsx src/operations/core-operation-executor.test.ts && tsx src/operations/bulk-mutation.test.ts && tsx src/operations/batch/scheduler.test.ts && tsx src/operations/batch/executor-policy.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && npm run build:app && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
|
|
48
51
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
49
52
|
"release:check": "node scripts/release-version.mjs check",
|
|
50
53
|
"release:tag-check": "node scripts/release-version.mjs tag",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
const npmCli = process.env.npm_execpath;
|
|
6
|
+
if (!npmCli) {
|
|
7
|
+
throw new Error("ci:verify must be launched through npm so npm_execpath is available");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
console.log(`CI environment: ${process.platform}/${process.arch} ${process.version}`);
|
|
11
|
+
runNpm(["--version"], "npm version");
|
|
12
|
+
runNpm(["run", "release:check"], "Release metadata");
|
|
13
|
+
runNpm(["run", "typecheck"], "Typecheck");
|
|
14
|
+
runNpm(["test"], "Full test suite");
|
|
15
|
+
runNpm(["run", "build"], "Build");
|
|
16
|
+
runNpm(["run", "lsp:interop"], "Optional LSP interoperability");
|
|
17
|
+
run(process.execPath, ["dist/cli.js", "doctor"], "Doctor");
|
|
18
|
+
|
|
19
|
+
console.log("CI verification passed.");
|
|
20
|
+
|
|
21
|
+
function runNpm(args, label) {
|
|
22
|
+
run(process.execPath, [npmCli, ...args], label);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function run(command, args, label) {
|
|
26
|
+
console.log(`\n== ${label} ==`);
|
|
27
|
+
const result = spawnSync(command, args, {
|
|
28
|
+
cwd: process.cwd(),
|
|
29
|
+
env: process.env,
|
|
30
|
+
stdio: "inherit",
|
|
31
|
+
windowsHide: true,
|
|
32
|
+
shell: false,
|
|
33
|
+
});
|
|
34
|
+
if (result.error) throw result.error;
|
|
35
|
+
if (result.status !== 0) {
|
|
36
|
+
throw new Error(`${label} failed with exit ${result.status ?? "unknown"}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -22,6 +22,27 @@ export function interactiveDebugConfigDir({ env = process.env, home = homedir()
|
|
|
22
22
|
return resolve(join(home, ".forgerelay", "debug"));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export function interactiveDebugUrls(configDir) {
|
|
26
|
+
let config = {};
|
|
27
|
+
try {
|
|
28
|
+
config = JSON.parse(readFileSync(join(configDir, "config.json"), "utf8"));
|
|
29
|
+
} catch {
|
|
30
|
+
// Keep the historical debug defaults when the selected config has not been created yet.
|
|
31
|
+
}
|
|
32
|
+
const configuredHost = typeof config.host === "string" && config.host.trim()
|
|
33
|
+
? config.host.trim()
|
|
34
|
+
: "127.0.0.1";
|
|
35
|
+
const host = configuredHost === "0.0.0.0" || configuredHost === "::"
|
|
36
|
+
? "127.0.0.1"
|
|
37
|
+
: configuredHost;
|
|
38
|
+
const port = Number.isInteger(config.port) && config.port > 0 && config.port <= 65_535
|
|
39
|
+
? config.port
|
|
40
|
+
: 7677;
|
|
41
|
+
const displayHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
42
|
+
const baseUrl = `http://${displayHost}:${port}`;
|
|
43
|
+
return { baseUrl, mcpUrl: `${baseUrl}/mcp` };
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
function interactiveDebugOwnerToken(configDir) {
|
|
26
47
|
const authPath = join(configDir, "auth.json");
|
|
27
48
|
let auth;
|
|
@@ -45,6 +66,7 @@ export function createInteractiveDebugEnvironment({
|
|
|
45
66
|
} = {}) {
|
|
46
67
|
const configDir = interactiveDebugConfigDir({ env, home });
|
|
47
68
|
const ownerToken = interactiveDebugOwnerToken(configDir);
|
|
69
|
+
const { baseUrl, mcpUrl } = interactiveDebugUrls(configDir);
|
|
48
70
|
mkdirSync(debugRoot, { recursive: true });
|
|
49
71
|
|
|
50
72
|
const debugEnv = { ...env };
|
|
@@ -67,7 +89,7 @@ export function createInteractiveDebugEnvironment({
|
|
|
67
89
|
}
|
|
68
90
|
debugEnv.FORGERELAY_CONFIG_DIR = configDir;
|
|
69
91
|
|
|
70
|
-
return { ownerToken, configDir, env: debugEnv };
|
|
92
|
+
return { ownerToken, configDir, baseUrl, mcpUrl, env: debugEnv };
|
|
71
93
|
}
|
|
72
94
|
|
|
73
95
|
export function createDebugEnvironment({
|
|
@@ -4,7 +4,11 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
createInteractiveDebugEnvironment,
|
|
9
|
+
interactiveDebugConfigDir,
|
|
10
|
+
interactiveDebugUrls,
|
|
11
|
+
} from "./runtime.mjs";
|
|
8
12
|
|
|
9
13
|
async function withDebugConfig(t) {
|
|
10
14
|
const home = await mkdtemp(join(tmpdir(), "forgerelay-interactive-debug-"));
|
|
@@ -45,6 +49,12 @@ test("interactive debug uses one dedicated persisted config under ~/.forgerelay/
|
|
|
45
49
|
assert.equal(result.ownerToken, "debug-owner-password-123456");
|
|
46
50
|
assert.equal(result.configDir, configDir);
|
|
47
51
|
assert.equal(result.env.FORGERELAY_CONFIG_DIR, configDir);
|
|
52
|
+
assert.equal(result.baseUrl, "http://127.0.0.1:7677");
|
|
53
|
+
assert.equal(result.mcpUrl, "http://127.0.0.1:7677/mcp");
|
|
54
|
+
assert.deepEqual(interactiveDebugUrls(configDir), {
|
|
55
|
+
baseUrl: "http://127.0.0.1:7677",
|
|
56
|
+
mcpUrl: "http://127.0.0.1:7677/mcp",
|
|
57
|
+
});
|
|
48
58
|
assert.equal(result.env.HOST, undefined);
|
|
49
59
|
assert.equal(result.env.PORT, undefined);
|
|
50
60
|
assert.equal(result.env.FORGERELAY_PUBLIC_BASE_URL, undefined);
|
|
@@ -60,7 +70,7 @@ test("interactive debug config directory may be explicitly relocated without fal
|
|
|
60
70
|
const { home } = await withDebugConfig(t);
|
|
61
71
|
const customDir = join(home, "custom-debug-config");
|
|
62
72
|
await mkdir(customDir, { recursive: true });
|
|
63
|
-
await writeFile(join(customDir, "config.json"), "
|
|
73
|
+
await writeFile(join(customDir, "config.json"), JSON.stringify({ host: "127.0.0.1", port: 6768 }) + "\n");
|
|
64
74
|
await writeFile(join(customDir, "auth.json"), JSON.stringify({ ownerToken: "custom-debug-password-123456" }) + "\n");
|
|
65
75
|
|
|
66
76
|
const result = createInteractiveDebugEnvironment({
|
|
@@ -71,4 +81,6 @@ test("interactive debug config directory may be explicitly relocated without fal
|
|
|
71
81
|
assert.equal(result.configDir, customDir);
|
|
72
82
|
assert.equal(result.ownerToken, "custom-debug-password-123456");
|
|
73
83
|
assert.equal(result.env.FORGERELAY_CONFIG_DIR, customDir);
|
|
84
|
+
assert.equal(result.baseUrl, "http://127.0.0.1:6768");
|
|
85
|
+
assert.equal(result.mcpUrl, "http://127.0.0.1:6768/mcp");
|
|
74
86
|
});
|
package/scripts/debug/serve.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { createInteractiveDebugEnvironment,
|
|
2
|
+
import { createInteractiveDebugEnvironment, repoRoot } from "./runtime.mjs";
|
|
3
3
|
|
|
4
|
-
const { configDir, env } = createInteractiveDebugEnvironment();
|
|
4
|
+
const { configDir, baseUrl, mcpUrl, env } = createInteractiveDebugEnvironment();
|
|
5
5
|
|
|
6
6
|
console.error("[forgerelay:debug] local debug server");
|
|
7
7
|
console.error(`[forgerelay:debug] debug config: ${configDir}/config.json`);
|
|
8
|
-
console.error(`[forgerelay:debug] health: ${
|
|
9
|
-
console.error(`[forgerelay:debug] MCP: ${
|
|
8
|
+
console.error(`[forgerelay:debug] health: ${baseUrl}/healthz`);
|
|
9
|
+
console.error(`[forgerelay:debug] MCP: ${mcpUrl}`);
|
|
10
10
|
console.error(`[forgerelay:debug] Owner password loaded from ${configDir}/auth.json`);
|
|
11
11
|
console.error("[forgerelay:debug] all runtime state is under .forgerelay-debug/");
|
|
12
12
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
|
|
7
|
+
const npmCli = process.env.npm_execpath;
|
|
8
|
+
if (!npmCli) {
|
|
9
|
+
throw new Error("release:pack must be launched through npm so npm_execpath is available");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const outputDir = resolve(process.cwd(), process.env.RELEASE_ARTIFACT_DIR ?? ".release-artifacts");
|
|
13
|
+
rmSync(outputDir, { recursive: true, force: true });
|
|
14
|
+
mkdirSync(outputDir, { recursive: true });
|
|
15
|
+
|
|
16
|
+
const result = spawnSync(
|
|
17
|
+
process.execPath,
|
|
18
|
+
[npmCli, "pack", "--pack-destination", outputDir],
|
|
19
|
+
{
|
|
20
|
+
cwd: process.cwd(),
|
|
21
|
+
env: process.env,
|
|
22
|
+
stdio: "inherit",
|
|
23
|
+
windowsHide: true,
|
|
24
|
+
shell: false,
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
if (result.error) throw result.error;
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
throw new Error(`npm pack failed with exit ${result.status ?? "unknown"}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const packages = readdirSync(outputDir).filter((name) => name.endsWith(".tgz"));
|
|
33
|
+
if (packages.length !== 1) {
|
|
34
|
+
throw new Error(`release:pack expected exactly one .tgz artifact, found ${packages.length}`);
|
|
35
|
+
}
|
|
36
|
+
console.log(`Verified npm artifact: ${packages[0]}`);
|